I have created a gridview, which shows videos from server. GridItem has video thumb image, and video duration.for loading video's thumb I am using UniversalImageloader and loading video duration by creating lazyloading using asynctask.Lazyloading works fine. but if someone scrolls gridview freequently,then video duration shows at wrong position. for creating Lazyloading I am following bellow link
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TextView durationTextView;
View view = null;
if (convertView == null) {
view = mInflater.inflate(R.layout.camera_roll_item, parent, false);
MediaItemViewHolder mediaItemViewHolder = new MediaItemViewHolder();
mediaItemViewHolder.highlightTagIcon = (ImageView) view.findViewById(R.id.iv_media_grid_item_highlight_tag);
mediaItemViewHolder.mediaTypeIcon = (ImageView) view.findViewById(R.id.iv_media_grid_item_type);
mediaItemViewHolder.mediaClipLength = (TextView) view.findViewById(R.id.tv_media_grid_item_length);
mediaItemViewHolder.mediaThumbnail = (ImageView) view.findViewById(R.id.iv_media_grid_item_thumbnail);
mediaItemViewHolder.cameraItemSelectedView = (RelativeLayout) view.findViewById(R.id.rl_item_selection_parent);
mediaItemViewHolder.progressContainer = (RelativeLayout) view.findViewById(R.id.rl_grid_loader_parent);
view.setTag(mediaItemViewHolder);
} else {
view = convertView;//(MediaItemViewHolder) convertView.getTag();
//mediaItemViewHolder.mediaClipLength.setText("");
Log.i(TAG, "set blank to ");
}
MediaItemViewHolder mediaItemViewHolder = (MediaItemViewHolder) convertView.getTag();
durationTextView = mediaItemViewHolder.mediaClipLength;
if (position >= mCameraMediaItems.size()) {
Log.d(TAG, "Index out of Bound, position:" + position + " - mCameraMediaItems.size():" + mCameraMediaItems.size());
return convertView;
}
MediaItemBean mediaItemBean = CameraMediaController.getInstance().getMediaItemByPosition(position);
mediaItemViewHolder.mediaClipLength.setVisibility(View.VISIBLE);
mediaItemViewHolder.highlightTagIcon.setVisibility(View.GONE);
if (mediaItemBean != null && mediaItemBean.getCameraMedia() != null) {
switch (mediaItemBean.getCameraMedia().getType()) {
case AppConstant.MEDIA_TYPE_VIDEO:
mediaItemViewHolder.mediaTypeIcon.setImageResource(R.drawable.icn_thumb_vid);
//VideoInfoAsyncTask loads data in this list
int videoDuration = mediaItemBean.getVideoDuration();
//mediaItemViewHolder.mediaClipLength.setTag(CameraMediaUtil.convertSecondsTimeToMinutesString(videoDuration));
Log.i(TAG, "VideoDuration " + videoDuration);
String resId = mediaItemBean.getCreatedId()+"video_media_duration_com.gopro.buckhorn";
Log.i(TAG, "RESID "+resId);
downloadDuration(resId, durationTextView, mediaItemViewHolder.highlightTagIcon, mediaItemBean);
break;
case MULTI_PHOTO:
String mulCount = String.valueOf(Controller.getInstance().getPhotoCountAtPosition(position));
Log.i("MULTI_SHOT_SECTION", "MultiShot "+mulCount);
mediaItemViewHolder.mediaTypeIcon.setImageResource(R.drawable.icn_thumb_burst);
mediaItemViewHolder.mediaClipLength.setText(mulCount);
break;
}
//Load image into image view from URL
String imageUri = mediaItemBean.getThumbnailUri().toString();
Log.i(TAG, "Thumb url :" + imageUri);
mediaItemViewHolder.progressContainer.setVisibility(View.VISIBLE);
DownloadImageUtil.getLoadImageInsatnce().downloadGridImage(imageUri,
mediaItemViewHolder.mediaThumbnail, R.drawable.thumb_load, mediaItemViewHolder.progressContainer);
}
return convertView;
}
private void downloadDuration(String resId, TextView textView, ImageView highlightTagIcon, MediaItemBean mediaItemBean) {
String duration = getVideoDurationFromCache(String.valueOf(resId));
Log.i(TAG, "downloadDuration " + duration);
if (duration == null) {
loadVideoDuration(resId, textView, highlightTagIcon, mediaItemBean);
textView.setText("");
} else {
cancelVideoDurationDownloaderTask(resId, textView);
if(mediaItemBean.getCameraMedia().getType() == AppConstant.MEDIA_TYPE_VIDEO){
textView.setText(duration);
if (mediaItemBean.isIsHighLightTags()) {
highlightTagIcon.setVisibility(View.VISIBLE);
}
}
}
}
private String getVideoDurationFromCache(String key) {
// First try the hard reference cache
synchronized (mMemoryCache) {
final String duration = mMemoryCache.get(key);
if (duration != null) {
// Bitmap found in hard cache
// Move element to first position, so that it is removed last
mMemoryCache.remove(key);
mMemoryCache.put(key, duration);
return duration;
}
}
return null;
}
private static class MediaItemViewHolder {
ImageView highlightTagIcon, mediaTypeIcon, mediaThumbnail;
TextView mediaClipLength;
RelativeLayout cameraItemSelectedView;
/* ProgressBar innerProgressBar;
ProgressBar outerProgressBar;*/
RelativeLayout progressContainer;
}
public class VideoDurationDownloaderTask extends AsyncTask<String, Void, String> {
private final WeakReference<TextView> videoDurationReference;
private final WeakReference<ImageView> hiliteTagImageViewWeakReference;
private String data = "";
private MediaItemBean mediaItemBean;
public VideoDurationDownloaderTask(TextView textView, ImageView hiliteTagIcon, MediaItemBean mediaItemBean) {
this.mediaItemBean = mediaItemBean;
videoDurationReference = new WeakReference<>(textView);
hiliteTagImageViewWeakReference = new WeakReference<>(hiliteTagIcon);
}
@Override
protected String doInBackground(String... params) {
data = params[0];
Log.i(TAG, "data in task "+data);
return downloadVideoDuration(mediaItemBean);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (isCancelled()) {
Log.i(TAG, "isCancelled " + result);
result = "";
}
addDurationToMemoryCache(data, result);
//noinspection ConstantConditions
if (videoDurationReference != null) {
TextView videoDuration = videoDurationReference.get();
Log.i(TAG, "videoDuration " + videoDuration);
VideoDurationDownloaderTask videoDurationDownloaderTask =
getTextViewDerationWorkerTask(videoDuration);
Log.i(TAG, "videoDurationDownloaderTask " + videoDurationDownloaderTask);
if (videoDuration != null) {
if (this == videoDurationDownloaderTask) {
if(mediaItemBean.getCameraMedia().getType() == AppConstant.MEDIA_TYPE_VIDEO) {
Log.i(TAG, "TAG VAL "+videoDuration.getTag());
videoDuration.setText(result);
videoDuration.setTag(new TextView(context));
if (mediaItemBean.isIsHighLightTags()) {
ImageView highlightTagIcon = hiliteTagImageViewWeakReference.get();
if (highlightTagIcon != null)
highlightTagIcon.setVisibility(View.VISIBLE);
}
}
}
}
}
}
}
private String downloadVideoDuration(MediaItemBean mediaItemBean) {
try {
if (media != null && mediaItemBean.getMedia() != null) {
int videoDuration = mediaItemBean.getVideoDuration();
Log.i(TAG, "Video has duration = " + videoDuration);
if (videoDuration == -1) {
CommandResult<Integer> duration =
media.getVideoDuration(mediaItemBean.getCameraMedia().getFilePath());
videoDuration = duration.getData();
mediaItemBean.setVideoDuration(videoDuration);
Log.i(TAG, "set Video Duration " + videoDuration);
}
return MediaUtil.convertSecondsTimeToMinutesString(videoDuration);
}
} catch (Exception e) {
Log.e(TAG, "Exception Occure while Getting video info:" + e.getMessage());
}
Log.i(TAG, "not fetch duration ");
return "";
}
public void loadVideoDuration(String resId, TextView textView, ImageView hiliteTagIcon, MediaItemBean mediaItemBean) {
if (cancelVideoDurationDownloaderTask(resId, textView)) {
final VideoDurationDownloaderTask task = new VideoDurationDownloaderTask(textView, hiliteTagIcon, mediaItemBean);
AsyncTextView asyncTextView = new AsyncTextView(context, task);
textView.setTag(asyncTextView);
task.execute(resId);
}
}
private boolean cancelVideoDurationDownloaderTask(String data, TextView textView) {
final VideoDurationDownloaderTask durationWorkerTask = getTextViewDerationWorkerTask(textView);
if (durationWorkerTask != null) {
final String textViewData = durationWorkerTask.data;
Log.i(TAG, textViewData + " textViewDataData, data " + data);
if (data != null && !textViewData.equalsIgnoreCase(data)) {
// Cancel previous task
Log.i(TAG, "Cancel previous task " + data);
durationWorkerTask.cancel(true);
} else {
// The same work is already in progress
Log.i(TAG, "same work is already in progress " + false);
return false;
}
}
// No task associated with the ImageView, or an existing task was
// cancelled
Log.i(TAG, "cancelVideoDurationDownloaderTask true");
return true;
}
static class AsyncTextView extends TextView {
private final WeakReference<VideoDurationDownloaderTask> textviewWorkerTaskReference;
public AsyncTextView(Context context, VideoDurationDownloaderTask textviewWorkerTask) {
super(context);
textviewWorkerTaskReference = new WeakReference<>(textviewWorkerTask);
}
public VideoDurationDownloaderTask getTextViewWorkerTask() {
return textviewWorkerTaskReference.get();
}
}
private static VideoDurationDownloaderTask getTextViewDerationWorkerTask(TextView textView) {
if(textView.getTag() != null){
Log.i(TAG, " textView.getTag() " + textView.getTag());
if (textView.getTag() instanceof AsyncTextView) {
Log.i(TAG, " Return Textview task");
final AsyncTextView asyncTextView = (AsyncTextView) textView.getTag();
return asyncTextView.getTextViewWorkerTask();
}
}
return null;
}
public void addDurationToMemoryCache(String key, String duration) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, duration);
}
}
Get rid of view
and use convertView
as you receive from method. Change to the following
//final is optional but if you need to use in thread
final MediaItemViewHolder mediaItemViewHolder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.camera_roll_item, parent, false);
mediaItemViewHolder = new MediaItemViewHolder();
mediaItemViewHolder.highlightTagIcon = (ImageView) convertView.findViewById(R.id.iv_media_grid_item_highlight_tag);
mediaItemViewHolder.mediaTypeIcon = (ImageView) convertView.findViewById(R.id.iv_media_grid_item_type);
mediaItemViewHolder.mediaClipLength = (TextView) convertView.findViewById(R.id.tv_media_grid_item_length);
mediaItemViewHolder.mediaThumbnail = (ImageView) convertView.findViewById(R.id.iv_media_grid_item_thumbnail);
mediaItemViewHolder.cameraItemSelectedView = (RelativeLayout) convertView.findViewById(R.id.rl_item_selection_parent);
mediaItemViewHolder.progressContainer = (RelativeLayout) convertView.findViewById(R.id.rl_grid_loader_parent);
convertView.setTag(mediaItemViewHolder);
} else {
mediaItemViewHolder = (MediaItemViewHolder)convertView.getTag();
Log.i(TAG, "set blank to ");
}
Now use mediaItemViewHolder.durationTextView.setText(....)
Update 1: I have figured out the problem. Why lazyloading update video duration at wrong gridview position. Discard the above if you want
downloadDuration(resId, durationTextView, mediaItemViewHolder.highlightTagIcon, mediaItemBean);
is the culprit. downloadDuration
runs in async mode and it has the reference of durationTextView
in before hand. Suppose the downloadDuration
is not finished and user scrolls the ListView
. After scrolling the ListView
downloadDuration
is finished, then this durationTextView
will be updated with the value but for the wrong ListView
item position and not for which position this was passed in downloadDuration
. Making final TextView durationTextView;
it as final will also not solve the problem.
Solution could be to show some kind of empty indicatior for duration and let the downloadDuration
finishes it's async job. Remove durationTextView
as a parameter and add position
as a parameter. After async job is done, you can update the bean list of type MediaItemBean
for that position. Now notify the adapter that some value has been changed and the ListView
will update itself accordingly. FYI RecyclerView
item update is more optimised than ListView
.
Update 2: You can fetch the items in advance and just map it to bean. Only one time async will run.
Update 3: In the meantime you can check bean for the particular ListView
item if it is 0 or not. If it is 0 show in the durationTextView.setText("00:00:00")
else call downloadDuration
and let it finish and update the duration value in bean. But still you need to notify
for updating item.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With