Skip to content

Commit

Permalink
Addresses issue #20, implements asset image view updated at measured …
Browse files Browse the repository at this point in the history
…latency. Support for pause, but not implemented currently
  • Loading branch information
multidynamic authored and 13rac1 committed Jan 23, 2015
1 parent 90ee420 commit bf48d3c
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 30 deletions.
196 changes: 177 additions & 19 deletions app/src/main/java/org/famsf/roundware/activity/ListenActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,22 @@

import org.famsf.roundware.R;
import org.famsf.roundware.Settings;
import org.famsf.roundware.utils.AssetData;
import org.famsf.roundware.utils.AssetImageManager;
import org.famsf.roundware.utils.LocationBg;
import org.famsf.roundware.utils.Utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class ListenActivity extends Activity {
private final static String LOGTAG = "Listen";
Expand All @@ -69,6 +78,8 @@ public class ListenActivity extends Activity {
private final static String AS_VOTE_TYPE_FLAG = "flag";
private final static String AS_VOTE_TYPE_LIKE = "like";

private final static int ASSET_IMAGE_LINGER_MS = 5200;

// fields
private ProgressDialog mProgressDialog;
private ViewFlipper mViewFlipper;
Expand All @@ -84,8 +95,12 @@ public class ListenActivity extends Activity {
private View mAssetImageLayout;
private ImageView mAssetImageView;
private TextView mAssetTextView;
private String mAssetImageUrl;
private String mAssetImageDescription;
private AssetData mPendingAsset = new AssetData(null,null);
private AssetData mCurrentAsset = mPendingAsset;


private PausableScheduledThreadPoolExecutor mEventPool;

private final Object mAssetImageLock = new Object();

// private ToggleButton mLikeButton;
Expand All @@ -97,6 +112,8 @@ public class ListenActivity extends Activity {
private String mContentFileDir;
private int mCurrentAssetId;
private int mPreviousAssetId;
private long mStartTime = 0;
private long mMetaPlayLatency = 0;
private AssetImageManager mAssetImageManager = null;

LocationListener mLocationListener = new LocationListener() {
Expand Down Expand Up @@ -167,6 +184,7 @@ public void onServiceDisconnected(ComponentName name) {
};



/**
* Handles events received from the RWService Android Service that we
* connect to. Since most operations of the service involve making calls
Expand All @@ -186,15 +204,12 @@ public void onReceive(Context context, Intent intent) {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if(mMetaPlayLatency == 0){
mMetaPlayLatency = System.currentTimeMillis() - mStartTime;
Log.v(LOGTAG, "Metadata latency estimated as " + mMetaPlayLatency);
}
} else if (RW.STREAM_METADATA_UPDATED.equals(intent.getAction())) {
if (D) { Log.d(LOGTAG, "RW_STREAM_METADATA_UPDATED"); }
// new asset started playing - update image display
// remove progress dialog when needed
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}

handleAssetChange( (Uri)intent.getParcelableExtra(RW.EXTRA_STREAM_METADATA_URI) );

} else if (RW.USER_MESSAGE.equals(intent.getAction())) {
Expand Down Expand Up @@ -222,6 +237,14 @@ protected void onCreate(Bundle savedInstanceState) {

setContentView(R.layout.activity_listen);
initUIWidgets();
mEventPool = new PausableScheduledThreadPoolExecutor(2);
mEventPool.setRejectedExecutionHandler( new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.w(LOGTAG, "Event was rejected!");
}
});
mEventPool.setKeepAliveTime(ASSET_IMAGE_LINGER_MS, TimeUnit.MILLISECONDS);

// connect to service started by other activity
try {
Expand Down Expand Up @@ -288,6 +311,10 @@ protected void onResume() {

@Override
protected void onDestroy() {
if(mEventPool != null){
mEventPool.purge();
mEventPool.shutdownNow();
}
if (rwConnection != null) {
unbindService(rwConnection);
}
Expand Down Expand Up @@ -462,7 +489,9 @@ private void startPlayback() {
showProgress(getString(R.string.starting_playback_title), getString(R.string.starting_playback_message), true, true);
mCurrentAssetId = -1;
mPreviousAssetId = -1;
setAssetImage(null, null);
AssetData assetData = new AssetData(null, null);
setPendingAsset(assetData);
setCurrentAsset(assetData);
mRwBinder.playbackStart(mTagsList);
}
mRwBinder.playbackFadeIn(mVolumeLevel);
Expand All @@ -478,7 +507,9 @@ private void stopPlayback() {
mRwBinder.playbackFadeOut();
mCurrentAssetId = -1;
mPreviousAssetId = -1;
setAssetImage(null, null);
AssetData assetData = new AssetData(null, null);
setPendingAsset(assetData);
setCurrentAsset(assetData);
updateUIState();
}

Expand All @@ -490,6 +521,9 @@ private void handleAssetChange(Uri uri) {
Log.d(LOGTAG, "handleAssetChange param null!");
return;
}
if(mStartTime == 0){
mStartTime = System.currentTimeMillis();
}
String assetValue = uri.getQueryParameter(RW.METADATA_URI_NAME_ASSET_ID);
int assetId = -1;
if(!TextUtils.isEmpty(assetValue)){
Expand All @@ -505,6 +539,13 @@ private void handleAssetChange(Uri uri) {
sendVotingState(mPreviousAssetId);

List<String> tags = RWUriHelper.getQueryParameterValues(uri, RW.METADATA_URI_NAME_TAGS);
if(tags.isEmpty()){
String remaining = RWUriHelper.getQueryParameter(uri, RW.METADATA_URI_NAME_REMAINING);
if(remaining != null && !remaining.equals("0") ){
// this metadata message is verbose, remaining asset probably still has same image
return;
}
}

// update display
String url = null;
Expand All @@ -529,8 +570,9 @@ private void handleAssetChange(Uri uri) {
}
}

setAssetImage(url, description);
updateAssetImageUi();
AssetData assetData = new AssetData(url, description);
setPendingAsset(assetData);
mEventPool.schedule(new AssetEvent(assetData), mMetaPlayLatency, TimeUnit.MILLISECONDS);
}


Expand All @@ -547,22 +589,37 @@ private void sendVotingState(int assetId) {
*/
}

private void setAssetImage(String url, String description){

private void setPendingAsset(AssetData asset) {
synchronized (mAssetImageLock) {
mPendingAsset = asset;
}
}
private void setCurrentAsset(AssetData asset){
synchronized (mAssetImageLock){
mAssetImageUrl = url;
mAssetImageDescription = description;
mCurrentAsset = asset;
}
}

private void updateAssetImageUi(){
if(mAssetImageView == null || mAssetTextView == null){
//panic
Log.w(LOGTAG, "An Asset Image View is null!");
return;
}
if(!mCurrentAsset.equals(mPendingAsset) && mCurrentAsset.url == null){
//pending appears valid, do not hide yet
return;
}
synchronized (mAssetImageLock) {
boolean hasUrl = !TextUtils.isEmpty(mAssetImageUrl);
boolean hasUrl = !TextUtils.isEmpty(mCurrentAsset.url);
if(hasUrl){
//load
Picasso picasso = Picasso.with(this);
// set below true, to view image source debugging
picasso.setIndicatorsEnabled(false);

picasso.load(mAssetImageUrl)
picasso.load(mCurrentAsset.url)
.into(mAssetImageView, new Callback() {
@Override
public void onSuccess() { }
Expand All @@ -575,7 +632,7 @@ public void onError() {
});
}
//TODO fade?
mAssetTextView.setText(mAssetImageDescription);
mAssetTextView.setText(mCurrentAsset.description);
mAssetImageLayout.setVisibility(hasUrl ? View.VISIBLE : View.INVISIBLE);
}
}
Expand Down Expand Up @@ -740,5 +797,106 @@ protected void onPostExecute(String result) {
}
}

private class AssetEvent implements Runnable{

private final AssetData assetData;
public AssetEvent(AssetData assetData){
this.assetData = assetData;
}

@Override
public void run() {
synchronized (mAssetImageLock){
if(mPendingAsset.equals(assetData)){
AssetData previousAsset = mCurrentAsset;
setCurrentAsset(assetData);
if(TextUtils.isEmpty(assetData.url) && !TextUtils.isEmpty(previousAsset.url)){
mEventPool.schedule(new AssetEvent(assetData), ASSET_IMAGE_LINGER_MS,
TimeUnit.MILLISECONDS);
}else{
Log.v(LOGTAG, "AssetEvent now, now drawing");
runOnUiThread(new Runnable() {
@Override
public void run() {
updateAssetImageUi();
}
});
}
}
}
}
}




/**
* Adds pausing, adapted from Android Developers Documentation
* http://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html
*/
private class PausableScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor{

public PausableScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize);
pauseQueue = new ArrayList<PausedRunnable>();
}
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();

private class PausedRunnable{
public final Runnable runnable;
public final long delay;
public PausedRunnable(Runnable runnable, long delay){
this.runnable = runnable;
this.delay = delay;
}
}

private ArrayList<PausedRunnable> pauseQueue;

private void add(Runnable r){
ScheduledFuture sf = (ScheduledFuture)r;
pauseQueue.add( new PausedRunnable(r, sf.getDelay(TimeUnit.MILLISECONDS)) );
sf.cancel(false);
}

protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();

if (isPaused) {
add(r);
}

pauseLock.unlock();

}

public void pause() {
pauseLock.lock();
try {
isPaused = true;
BlockingQueue<Runnable> queue = getQueue();
for(Runnable r : queue){
add(r);
}
} finally {
pauseLock.unlock();
}
}

public void resume() {
pauseLock.lock();
try {
isPaused = false;
for(PausedRunnable pausedRunnable : pauseQueue){
this.schedule(pausedRunnable.runnable, pausedRunnable.delay, TimeUnit.MILLISECONDS);
}
} finally {
pauseLock.unlock();
}
}
}

}

27 changes: 27 additions & 0 deletions app/src/main/java/org/famsf/roundware/utils/AssetData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.famsf.roundware.utils;

/**
* A simple class with two strings
*/
public class AssetData {
public final String url;
public final String description;

public AssetData(String url, String description){
this.url = url;
this.description = description;
}

@Override
public boolean equals(Object o) {
if(o instanceof AssetData){
AssetData other = (AssetData)o;
boolean urlTest = other.url == null ? this.url == null : other.url.equals(this.url);
boolean descriptionTest = other.description == null ? this.description == null :
other.description.equals(this.description);
return urlTest && descriptionTest;
}
return false;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ public class AssetImageManager {
public final static String PREFS_IMAGE_PREFIX = "asset_img_";
public final static String PREFS_DESCRIPTION_PREFIX = "asset_txt_";

private static class AssetData {
public final String urlSuffix;
public final String description;

public AssetData(String urlSuffix, String description){
this.urlSuffix = urlSuffix;
this.description = description;
}
}

private SparseArray<AssetData> map = new SparseArray<AssetData>(INITIAL_SIZE);
private final String hostUrl;

Expand Down Expand Up @@ -88,7 +78,7 @@ private String getImageUrlSuffix(int tagId){
String out = null;
AssetData data = map.get(tagId);
if(data != null){
out = data.urlSuffix;
out = data.url;
}
if(TextUtils.isEmpty(out)){
// fall back on SharedPreferences cache
Expand Down

0 comments on commit bf48d3c

Please sign in to comment.