Playing video using textureview in recyclerview - android

I am trying to implement a list with videos like vine or Instagram app. Where they play video plays when list item is shown or fully visible and video pauses when list item gets hided. I am using textureview with media player to play a video from url and added it as list item in recyclerview. Following is my code.
VideosAdapter Class:
public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> {
Context context;
private ArrayList<String> urls;
public static class ViewHolder extends RecyclerView.ViewHolder {
public LinearLayout layout;
public TextView textView;
public ViewHolder(View v) {
super(v);
layout = (LinearLayout) v.findViewById(R.id.linearLayout);
textView = (TextView) v.findViewById(R.id.textView);
}
}
public VideosAdapter(Context context, ArrayList<String> urls) {
this.context = context;
this.urls = urls;
}
// Create new views (invoked by the layout manager)
#Override
public VideosAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String url = urls.get(position);
holder.textView.setText(url);
playVideo(holder, url);
}
#Override
public int getItemCount() {
return urls.size();
}
private void playVideo(ViewHolder holder, String url)
{
final CustomVideoPlayer vid = new CustomVideoPlayer(String.valueOf(url), context);
holder.layout.addView(vid);
holder.layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
vid.changePlayState();
}
});
}
}
CustomVideoPlayer Class:
public class CustomVideoPlayer extends TextureView implements TextureView.SurfaceTextureListener
{
Context context;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;
public CustomVideoPlayer(Context context, AttributeSet attrs)
{
super(context, attrs);
this.context = context;
}
public CustomVideoPlayer(String ur, Context context)
{
super(context);
this.setSurfaceTextureListener(this);
this.url = ur;
this.context = context;
}
#Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int arg1, int arg2) {
this.s = surface;
Log.d("url", this.url);
startVideo(surface);
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {
return true;
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,int arg2) {
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
}
public void setVideo(String url)
{
this.url = url;
}
public void startVideo(SurfaceTexture t)
{
this.surface = new Surface(t);
this.mp = new MediaPlayer();
this.mp.setSurface(this.surface);
try {
Uri uri = Uri.parse(this.url);
this.mp.setDataSource(url);
this.mp.prepareAsync();
this.mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
mp.start();
}
});
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (IllegalStateException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
try {
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public void changePlayState()
{
if(this.mp.isPlaying())
this.mp.pause();
else
this.mp.start();
}
}
When i run this code there are multiple issues in it.
1) First two items/videos buffers and play fine. But when i scroll it does not load third video and first video also gets removed from the list.
2) On scroll videos/list items starts buffering again for the item that was already buffered.
3) On fast scroll list gets too laggy and get stuck and crashes.
Attached is the image of logcat that i get while list scroll and video playing.
Can anyone guide me through this? What is the right way to create a list like vine app?

I was able to achieve that by first downloading the videos from url and then playing it with custom player. Here is how i did in case if anyone else needed that:
1) Get all url's need to be played
2) Start downloading videos (in queue) from urls in local storage and keep a flag in preferences (that a video is already downloaded or not)
3) Assign urls to Adapter in which initialize object of video player controller that handles video playbacks
4) Set addOnScrollListener to check which position/video is currently visible and check if video is already downloaded or not if yes then play it.
Following is complete code:
MainActivity
public class MainActivity extends ActionBarActivity implements IVideoDownloadListener {
private static String TAG = "MainActivity";
private Context context;
private RecyclerView mRecyclerView;
private ProgressBar progressBar;
private VideosAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private ArrayList<Video> urls;
VideosDownloader videosDownloader;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = MainActivity.this;
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
urls = new ArrayList<Video>();
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new VideosAdapter(MainActivity.this, urls);
mRecyclerView.setAdapter(mAdapter);
videosDownloader = new VideosDownloader(context);
videosDownloader.setOnVideoDownloadListener(this);
if(Utils.hasConnection(context))
{
getVideoUrls();
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LinearLayoutManager layoutManager = ((LinearLayoutManager) recyclerView.getLayoutManager());
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
Video video;
if (urls != null && urls.size() > 0)
{
if (findFirstCompletelyVisibleItemPosition >= 0) {
video = urls.get(findFirstCompletelyVisibleItemPosition);
mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(findFirstCompletelyVisibleItemPosition);
mAdapter.videoPlayerController.handlePlayBack(video);
}
else
{
video = urls.get(firstVisiblePosition);
mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(firstVisiblePosition);
mAdapter.videoPlayerController.handlePlayBack(video);
}
}
}
}
});
}
else
Toast.makeText(context, "No internet available", Toast.LENGTH_LONG).show();
}
#Override
public void onVideoDownloaded(Video video) {
mAdapter.videoPlayerController.handlePlayBack(video);
}
private void getVideoUrls()
{
Video video1 = new Video("0", "1", "http://techslides.com/demos/sample-videos/small.mp4");
urls.add(video1);
Video video2 = new Video("1", "2", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
urls.add(video2);
Video video3 = new Video("2", "3", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
urls.add(video3);
Video video4 = new Video("3", "4", "http://dev.exiv2.org/attachments/341/video-2012-07-05-02-29-27.mp4");
urls.add(video4);
Video video5 = new Video("4", "5", "http://techslides.com/demos/sample-videos/small.mp4");
urls.add(video5);
Video video6 = new Video("5", "6", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
urls.add(video6);
Video video7 = new Video("6", "7", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
urls.add(video7);
mAdapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
videosDownloader.startVideosDownloading(urls);
}
}
VideosAdapter
public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> {
private static String TAG = "VideosAdapter";
Context context;
private ArrayList<Video> urls;
public VideoPlayerController videoPlayerController;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public ProgressBar progressBar;
public RelativeLayout layout;
public ViewHolder(View v) {
super(v);
layout = (RelativeLayout) v.findViewById(R.id.layout);
textView = (TextView) v.findViewById(R.id.textView);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
}
}
public VideosAdapter(Context context, final ArrayList<Video> urls) {
this.context = context;
this.urls = urls;
videoPlayerController = new VideoPlayerController(context);
}
// Create new views (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);
Configuration configuration = context.getResources().getConfiguration();
int screenWidthDp = configuration.screenWidthDp; //The current width of the available screen space, in dp units, corresponding to screen width resource qualifier.
int smallestScreenWidthDp = configuration.smallestScreenWidthDp; //The smallest screen size an application will see in normal operation, corresponding to smallest screen width resource qualifier.
ViewHolder viewHolder = new ViewHolder(v);
int screenWidthPixels = Utils.convertDpToPixel(screenWidthDp, context);
RelativeLayout.LayoutParams rel_btn = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, screenWidthPixels);
viewHolder.layout.setLayoutParams(rel_btn);
return viewHolder;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Video video = urls.get(position);
holder.textView.setText("Video " + video.getId());
final VideoPlayer videoPlayer = new VideoPlayer(context);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
videoPlayer.setLayoutParams(params);
holder.layout.addView(videoPlayer);
videoPlayerController.loadVideo(video, videoPlayer, holder.progressBar);
videoPlayer.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
videoPlayer.changePlayState();
}
});
}
#Override
public void onViewRecycled(ViewHolder holder) {
super.onViewRecycled(holder);
Log.d(TAG, "onViewRecycledCalled");
holder.layout.removeAllViews();
}
#Override
public int getItemCount() {
return urls.size();
}
}
VideosDownloader
public class VideosDownloader {
private static String TAG = "VideosDownloader";
Context context;
FileCache fileCache;
IVideoDownloadListener iVideoDownloadListener;
public VideosDownloader(Context context) {
this.context = context;
fileCache = new FileCache(context);
}
/////////////////////////////////////////////////////////////////
// Start downloading all videos from given urls
public void startVideosDownloading(final ArrayList<Video> videosList)
{
Thread thread = new Thread(new Runnable() {
#Override
public void run()
{
for(int i=0; i<videosList.size(); i++)
{
final Video video = videosList.get(i);
String id = video.getId();
String url = video.getUrl();
String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
if(!isVideoAvailable)
{
//Download video from url
String downloadedPath = downloadVideo(url);
//Log.i(TAG, "Vides downloaded at: " + downloadedPath);
Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
Utils.savePreferences(context, video.getUrl(), "true");
iVideoDownloadListener.onVideoDownloaded(video);
}
});
}
}
}
});
thread.start();
}
/////////////////////////////////////////////////////////////////
private String downloadVideo(String urlStr)
{
URL url = null;
File file = null;
try
{
file = fileCache.getFile(urlStr);
url = new URL(urlStr);
long startTime = System.currentTimeMillis();
URLConnection ucon = null;
ucon = url.openConnection();
InputStream is = ucon.getInputStream();
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
FileOutputStream outStream = new FileOutputStream(file);
byte[] buff = new byte[5 * 1024];
//Read bytes (and store them) until there is nothing more to read(-1)
int len;
while ((len = inStream.read(buff)) != -1) {
outStream.write(buff, 0, len);
}
//clean up
outStream.flush();
outStream.close();
inStream.close();
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return file.getAbsolutePath();
}
public void setOnVideoDownloadListener(IVideoDownloadListener iVideoDownloadListener) {
this.iVideoDownloadListener = iVideoDownloadListener;
}
}
VideoPlayerController
public class VideoPlayerController {
private static String TAG = "VideoPlayerController";
Context context;
FileCache fileCache;
int currentPositionOfItemToPlay = 0;
Video currentPlayingVideo;
private Map<String, VideoPlayer> videos = Collections.synchronizedMap(new WeakHashMap<String, VideoPlayer>());
private Map<String, ProgressBar> videosSpinner = Collections.synchronizedMap(new WeakHashMap<String, ProgressBar>());
public VideoPlayerController(Context context) {
this.context = context;
fileCache = new FileCache(context);
}
public void loadVideo(Video video, VideoPlayer videoPlayer, ProgressBar progressBar) {
//Add video to map
videos.put(video.getIndexPosition(), videoPlayer);
videosSpinner.put(video.getIndexPosition(), progressBar);
handlePlayBack(video);
}
//This method would check two things
//First if video is downloaded or its local path exist
//Second if the videoplayer of this video is currently showing in the list or visible
public void handlePlayBack(Video video)
{
//Check if video is available
if(isVideoDownloaded(video))
{
// then check if it is currently at a visible or playable position in the listview
if(isVideoVisible(video))
{
//IF yes then playvideo
playVideo(video);
}
}
}
private void playVideo(final Video video)
{
//Before playing it check if this video is already playing
if(currentPlayingVideo != video)
{
//Start playing new url
if(videos.containsKey(video.getIndexPosition()))
{
final VideoPlayer videoPlayer2 = videos.get(video.getIndexPosition());
String localPath = fileCache.getFile(video.getUrl()).getAbsolutePath();
if(!videoPlayer2.isLoaded)
{
videoPlayer2.loadVideo(localPath, video);
videoPlayer2.setOnVideoPreparedListener(new IVideoPreparedListener() {
#Override
public void onVideoPrepared(Video mVideo) {
//Pause current playing video if any
if(video.getIndexPosition() == mVideo.getIndexPosition())
{
if(currentPlayingVideo!=null)
{
VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
videoPlayer1.pausePlay();
}
videoPlayer2.mp.start();
currentPlayingVideo = mVideo;
}
}
});
}
else
{
//Pause current playing video if any
if(currentPlayingVideo!=null)
{
VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
videoPlayer1.pausePlay();
}
boolean isStarted = videoPlayer2.startPlay();
{
//Log.i(TAG, "Started playing Video Index: " + video.getIndexPosition());
//Log.i(TAG, "Started playing Video: " + video.getUrl());
}
currentPlayingVideo = video;
}
}
}
else
{
//Log.i(TAG, "Already playing Video: " + video.getUrl());
}
}
private boolean isVideoVisible(Video video) {
//To check if the video is visible in the listview or it is currently at a playable position
//we need the position of this video in listview and current scroll position of the listview
int positionOfVideo = Integer.valueOf(video.getIndexPosition());
if(currentPositionOfItemToPlay == positionOfVideo)
return true;
return false;
}
private boolean isVideoDownloaded(Video video) {
String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
if(isVideoAvailable)
{
//If video is downloaded then hide its progress
hideProgressSpinner(video);
return true;
}
showProgressSpinner(video);
return false;
}
private void showProgressSpinner(Video video) {
ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
if(progressBar!=null)
progressBar.setVisibility(View.VISIBLE);
}
private void hideProgressSpinner(Video video) {
ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
if(progressBar!=null && progressBar.isShown())
{
progressBar.setVisibility(View.GONE);
Log.i(TAG, "ProgressSpinner Hided Index: " + video.getIndexPosition());
}
}
public void setcurrentPositionOfItemToPlay(int mCurrentPositionOfItemToPlay) {
currentPositionOfItemToPlay = mCurrentPositionOfItemToPlay;
}
}
VideoPlayer
public class VideoPlayer extends TextureView implements TextureView.SurfaceTextureListener {
private static String TAG = "VideoPlayer";
/**This flag determines that if current VideoPlayer object is first item of the list if it is first item of list*/
boolean isFirstListItem;
boolean isLoaded;
boolean isMpPrepared;
IVideoPreparedListener iVideoPreparedListener;
Video video;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;
public VideoPlayer(Context context) {
super(context);
}
public VideoPlayer(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void loadVideo(String localPath, Video video) {
this.url = localPath;
this.video = video;
isLoaded = true;
if (this.isAvailable()) {
prepareVideo(getSurfaceTexture());
}
setSurfaceTextureListener(this);
}
#Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
isMpPrepared = false;
prepareVideo(surface);
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if(mp!=null)
{
mp.stop();
mp.reset();
mp.release();
mp = null;
}
return false;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
public void prepareVideo(SurfaceTexture t)
{
this.surface = new Surface(t);
mp = new MediaPlayer();
mp.setSurface(this.surface);
try {
mp.setDataSource(url);
mp.prepareAsync();
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
isMpPrepared = true;
mp.setLooping(true);
iVideoPreparedListener.onVideoPrepared(video);
}
});
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (IllegalStateException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
try {
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
#Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
}
public boolean startPlay()
{
if(mp!=null)
if(!mp.isPlaying())
{
mp.start();
return true;
}
return false;
}
public void pausePlay()
{
if(mp!=null)
mp.pause();
}
public void stopPlay()
{
if(mp!=null)
mp.stop();
}
public void changePlayState()
{
if(mp!=null)
{
if(mp.isPlaying())
mp.pause();
else
mp.start();
}
}
public void setOnVideoPreparedListener(IVideoPreparedListener iVideoPreparedListener) {
this.iVideoPreparedListener = iVideoPreparedListener;
}
}
IVideoDownloadListener
public interface IVideoDownloadListener {
public void onVideoDownloaded(Video video);
}
IVideoPreparedListener
public interface IVideoPreparedListener {
public void onVideoPrepared(Video video);
}

Why don't you add the custom video view in the layout file 'view_main' itself.
Check the visibility of the video view and play only if the view is visible.
public static boolean isViewVisible(View subView, View parentView) {
Rect scrollBounds = new Rect();
parentView.getHitRect(scrollBounds);
if (subView.getLocalVisibleRect(scrollBounds)) {
return true;
}
return false;
}
Code for checking visiblity. Call this in scroll state changed listener when the scroll state is idle.
Also you will have to use an AsyncTask for downloading videos ,but only download one video at a time or you might get out of memory error.

You should maintain a cache of videos locally by downloading them at backend and play one video at a time from local memory to keep the list scroll smooth.

Related

items not removed correctly from RecyclerView

I heard that if we want to remove any item from our RecyclerView we can use this method for example:
private void removeItem(int position){
recordingArrayList.remove(position);
notifyItemRemoved(position);
notifyItemChanged(position, recordingArrayList.size());
notifyDataSetChanged();
}
But seems I missed something, here is my RecordingListActivity .java:
public class RecordingListActivity extends AppCompatActivity {
private RecyclerView recyclerViewRecordings;
private BottomNavigationView bottomNavigationView;
private BottomAppBar bottomAppBar;
private ArrayList<Recording> recordingArrayList;
private FloatingActionButton floatingActionButton;
private RecordingAdapter recordingAdapter;
private TextView textViewNoRecordings;
private TimeAgo timeAgo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recording_list);
timeAgo = new TimeAgo();
initViews();
fetchRecordings();
}
private void initViews() {
recordingArrayList = new ArrayList<Recording>();
floatingActionButton = findViewById(R.id.fab);
recyclerViewRecordings = findViewById(R.id.recyclerViewRecordings);
recyclerViewRecordings.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
recyclerViewRecordings.setHasFixedSize(true);
new ItemTouchHelper(simpleCallback).attachToRecyclerView(recyclerViewRecordings);
textViewNoRecordings = findViewById(R.id.textViewNoRecordings);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (v.getId() == R.id.fab){
openHome();
}
}
});
}
private void openHome(){
Intent home = new Intent(RecordingListActivity.this, MainActivity.class);
home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(home);
}
private void fetchRecordings() {
File root = android.os.Environment.getExternalStorageDirectory();
String path = root.getAbsolutePath() + "/AbdulhaleemRecorderApp/Audios";
Log.d("Files", "Path: " + path);
File directory = new File(path);
File[] files = directory.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
Log.d("Files", "FileName:" + files[i].getName());
String fileName = files[i].getName();
String recordingUri = root.getAbsolutePath() + "/AbdulhaleemRecorderApp/Audios/" + fileName;
Double size = (double) files[i].length() / 1024;
String time = timeAgo.getTimeAgo(files[i].lastModified());
String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
Recording recording = new Recording(recordingUri, fileName, date, size, time,false);
recordingArrayList.add(recording);
}
textViewNoRecordings.setVisibility(View.GONE);
recyclerViewRecordings.setVisibility(View.VISIBLE);
setAdaptertoRecyclerView();
} else {
textViewNoRecordings.setVisibility(View.VISIBLE);
recyclerViewRecordings.setVisibility(View.GONE);
}
}
private void setAdaptertoRecyclerView() {
recordingAdapter = new RecordingAdapter(this, recordingArrayList);
recyclerViewRecordings.setAdapter(recordingAdapter);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return false;
}
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getBindingAdapterPosition();
removeItem(position);
}
};
private void removeItem(int position){
recordingArrayList.remove(position);
recordingAdapter.notifyItemRemoved(position);
recordingAdapter.notifyItemChanged(position, recordingArrayList.size());
recordingAdapter.notifyDataSetChanged();
}
here is my RecordingAdapter.java
public class RecordingAdapter extends RecyclerView.Adapter<RecordingAdapter.ViewHolder> {
private ArrayList<Recording> recordingArrayList;
private Context context;
private MediaPlayer mPlayer;
private boolean isPlaying = false;
private int last_index = -1;
public RecordingAdapter(Context context, ArrayList<Recording> recordingArrayList) {
this.context = context;
this.recordingArrayList = recordingArrayList;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.recording_item_layout, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
setUpData(holder, position);
}
#Override
public int getItemCount() {
return recordingArrayList.size();
}
private void setUpData(ViewHolder holder, int position) {
Recording recording = recordingArrayList.get(position);
holder.textViewName.setText(recording.getFileName());
holder.textViewDate.setText(recording.getDate());
holder.textViewSize.setText(String.format("%.2f", (double) (recording.getSize())) + " KB");
holder.textViewTime.setText(recording.getTime());
if (recording.isPlaying()) {
holder.imageViewPlay.setImageResource(R.drawable.ic_pause);
TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView);
holder.seekBar.setVisibility(View.VISIBLE);
holder.seekUpdation(holder);
} else {
holder.imageViewPlay.setImageResource(R.drawable.ic_play);
TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView);
holder.seekBar.setVisibility(View.GONE);
}
holder.manageSeekBar(holder);
}
public class ViewHolder extends RecyclerView.ViewHolder {
SeekBar seekBar;
ImageView imageViewPlay;
ImageView imageViewShare;
ImageView imageViewEdit;
TextView textViewName;
TextView textViewDate;
TextView textViewSize;
TextView textViewTime;
TextView textViewPhoneNumber;
private String recordingUri;
private int lastProgress = 0;
private Handler mHandler = new Handler();
ViewHolder holder;
public ViewHolder(View itemView) {
super(itemView);
final Context vhContext = itemView.getContext();
seekBar = itemView.findViewById(R.id.seekBar);
imageViewPlay = itemView.findViewById(R.id.imageViewPlay);
imageViewShare = itemView.findViewById(R.id.imageViewShare);
imageViewEdit = itemView.findViewById(R.id.imageViewEdit);
textViewName = itemView.findViewById(R.id.textViewRecordingname);
textViewDate = itemView.findViewById(R.id.textViewDate);
textViewSize = itemView.findViewById(R.id.textViewSize);
textViewTime = itemView.findViewById(R.id.textViewTime);
textViewPhoneNumber = itemView.findViewById(R.id.textViewPhoneNumber);
imageViewShare.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
Recording recording = recordingArrayList.get(position);
recordingUri = recording.getUri();
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("audio/mp3");
share.putExtra(Intent.EXTRA_STREAM, recordingUri);
vhContext.startActivity(Intent.createChooser(share, "Share " + recording.getFileName() + " using:"));
}
});
imageViewPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = getAdapterPosition();
Recording recording = recordingArrayList.get(position);
recordingUri = recording.getUri();
if (isPlaying) {
stopPlaying();
if (position == last_index) {
recording.setPlaying(false);
stopPlaying();
notifyItemChanged(position);
} else {
markAllPaused();
recording.setPlaying(true);
notifyItemChanged(position);
startPlaying(recording, position);
last_index = position;
}
} else {
startPlaying(recording, position);
recording.setPlaying(true);
seekBar.setMax(mPlayer.getDuration());
Log.d("isPlayin", "False");
notifyItemChanged(position);
last_index = position;
}
}
});
}
public void manageSeekBar(ViewHolder holder) {
holder.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mPlayer != null && fromUser) {
mPlayer.seekTo(progress);
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
private void markAllPaused() {
for (int i = 0; i < recordingArrayList.size(); i++) {
recordingArrayList.get(i).setPlaying(false);
recordingArrayList.set(i, recordingArrayList.get(i));
}
notifyDataSetChanged();
}
Runnable runnable = new Runnable() {
#Override
public void run() {
seekUpdation(holder);
}
};
private void seekUpdation(ViewHolder holder) {
this.holder = holder;
if (mPlayer != null) {
int mCurrentPosition = mPlayer.getCurrentPosition();
holder.seekBar.setMax(mPlayer.getDuration());
holder.seekBar.setProgress(mCurrentPosition);
lastProgress = mCurrentPosition;
}
mHandler.postDelayed(runnable, 100);
}
private void stopPlaying() {
try {
mPlayer.release();
} catch (Exception e) {
e.printStackTrace();
}
mPlayer = null;
isPlaying = false;
}
private void startPlaying(final Recording audio, final int position) {
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(recordingUri);
mPlayer.prepare();
mPlayer.start();
} catch (IOException e) {
Log.e("LOG_TAG", "prepare() failed");
}
//showing the pause button
seekBar.setMax(mPlayer.getDuration());
isPlaying = true;
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
audio.setPlaying(false);
notifyItemChanged(position);
}
});
}
when I swap the item it seems it deleted, but when I open another activity and come back again the deleted item comes back again like it is never deleted, what is the problem?
Corresponding to your code it's expected behaviour. So, your code fetches files data from device file system in onCreate() method of your activity and creates a list of representation of the files.
When recordingArrayList.remove(position); is invoked, it removes item from the representation list, but not file from file system.
When you call another activity and goes back, activity with the list is created again and onCreate() is called again and your code fetches again all the files from device file system and shows it in a list.
Possible solution can be: remove file from file system, if you have read-write access to this folder or save a list in your app. For example, in Room database and add to your file representation field isDeleted and use data from database to show in a list.
Hope it helps!
You are not saving the changes to the file and when you reload the activity, the array is fetched again from the file which hasn't been updated.
Your removeItem function should be in adapter class or after removing you should pass changed list to adapter and after that notify changes. In your case you actually not removing item from adapter (because list in activity and list in adapter are different (different instances)).
You just need to delete the file name from where you are reading the file too, cause when you remove an item from the list it isn't removed from the file. So when removing remove its element from the list too.
Try to keep your logic for adapter change in your adapter only.
I'll suggest you make a method name setData(ArrayList<Recording> recordingsList) in your recycler view.
In RecordingAdapter
public class RecordingAdapter extends RecyclerView.Adapter<RecordingAdapter.ViewHolder> {
private ArrayList<Recording> recordingArrayList;
private Context context;
public RecordingAdapter(Context context, ArrayList<Recording> recordingArrayList) {
this.context = context;
this.recordingArrayList = recordingArrayList;
}
// added
public setData(ArrayList<Recording> recordingsList) {
this.recordingArrayList = recordingsList;
notifyDataSetChanged();
}
}
And when you swipe to delete call the RecordingAdapter setData(RecordingLists) method
RecordingListActivity
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getBindingAdapterPosition();
recordingArrayList.remove(position);
recordingAdapter.setData(recordingArrayList); // changed
}
};
First remove item from the list then refresh your adapter by notifyItemRangeChanged with new size of the list and most important you need to update the data which you are fetching for recycler-view.
recordingArrayList.remove(position);
notifyItemRangeChanged(position,recordingArrayList.size());

Glide and RecyclerView - Images messed up

I'm trying to create a RecyclerView with ImageViews. Here is what I get:
As you can see all the artist covers are messed up and in the wrong order. If I scroll or reload, the respective images might change.
Here is my adapter onBindViewHolder:
public class ArtistsAdapter extends RecyclerView.Adapter<ArtistViewHolder> implements ListPreloader.PreloadModelProvider {
private static final String TAG = ArtistsAdapter.class.getSimpleName();
private final LayoutInflater mInflater;
private final GlideRequest<Bitmap> fullRequest;
ArtistsAdapter(Context context) {
this.mContext = context;
this.artists = new ArrayList<>();
this.mInflater = LayoutInflater.from(context);
this.fullRequest = GlideApp.with(context).asBitmap();
}
private final Context mContext;
public List<CompactdArtist> getArtists() {
return artists;
}
void swapArtists (List<CompactdArtist> artists) {
this.artists.clear();
this.artists.addAll(artists);
notifyDataSetChanged();
}
private final List<CompactdArtist> artists;
#Override
public ArtistViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_artist_item, parent, false);
return new ArtistViewHolder(view);
}
#Override
public void onViewRecycled(ArtistViewHolder holder) {
super.onViewRecycled(holder);
Log.d(TAG, "onViewRecycled: ");
GlideApp.with(mContext).clear(holder.artistImageView);
holder.artistImageView.setImageDrawable(null);
}
#Override
public void onBindViewHolder(final ArtistViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder: " + position);
final CompactdArtist current = artists.get(position);
// holder.setIsRecyclable(false);
holder.artistImageView.setImageDrawable(null);
try {
fullRequest.load(new MediaCover(current))
.fallback(ImageUtils.getFallback(mContext))
.into(new ImageViewTarget<Bitmap>(holder.artistImageView) {
#Override
protected void setResource(#Nullable Bitmap resource) {
if (resource == null) {
// holder.setIsRecyclable(true);
return;
}
holder.artistImageView.setImageBitmap(resource);
int color = Palette.from(resource).generate().getMutedColor(0xFFFFFF);
try {
current.fetch();
} catch (CouchbaseLiteException e) {
e.printStackTrace();
holder.artistNameText.setText(R.string.unknown_artist_name);
}
holder.artistBackground.setBackgroundColor(color);
holder.bindArtist(current);
holder.artistSub.setText(current.getAlbumCount() + " albums");
holder.artistNameText.setText(current.getName());
// holder.setIsRecyclable(true);
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
// holder.setIsRecyclable(true);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public long getItemId(int position) {
return artists.get(position).getId().hashCode();
}
#Override
public int getItemCount() {
return this.artists.size();
}
#NonNull
#Override
public List getPreloadItems(int position) {
return this.artists.subList(position, position + 1);
}
#Nullable
#Override
public RequestBuilder<?> getPreloadRequestBuilder(Object item) {
MediaCover cover = (MediaCover) item;
return fullRequest.clone().thumbnail(0.2f).load(cover);
}
}
Here is code in my fragment container:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_artists, container, false);
mArtistRecyclerView = rootView.findViewById(R.id.artists_recyclerview);
LinearLayoutManager layoutManager = new GridLayoutManager(getContext(), 3);
mArtistRecyclerView.setLayoutManager(layoutManager);
mArtistsAdapter = new ArtistsAdapter(getActivity());
mArtistRecyclerView.setAdapter(mArtistsAdapter);
mArtistRecyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
#Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
ArtistViewHolder artistViewHolder = (ArtistViewHolder) holder;
GlideApp.with(holder.itemView).clear(artistViewHolder.getArtistImage());
}
});
FixedPreloadSizeProvider<MediaCover> coverSizeProvider = new FixedPreloadSizeProvider<>(64, 64);
RecyclerViewPreloader<MediaCover> preloader = new RecyclerViewPreloader<MediaCover>(GlideApp.with(this), mArtistsAdapter, coverSizeProvider, 2);
return rootView;
}
As a bonus, I add the fetcher for my MediaCover:
/**
* Created by vinz243 on 13/12/2017.
*/
public class MediaCoverFetcher implements DataFetcher<InputStream> {
private final MediaCover mediaCover;
private boolean cancelled = false;
private InputStream stream;
public MediaCoverFetcher(MediaCover mediaCover) {
this.mediaCover = mediaCover;
}
#Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
CompactdArtwork artwork = mediaCover.getArtwork();
try {
stream = artwork.getImage(ArtworkSize.LARGE);
if (cancelled) {
callback.onLoadFailed(new RuntimeException("Cancelled"));
} else {
callback.onDataReady(stream);
}
} catch (NullPointerException e) {
callback.onLoadFailed(e);
}
}
#Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void cancel() {
cancelled = true;
}
#NonNull
#Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
#NonNull
#Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
I guess it comes from the recycling part, but I don't know how to fix the behavior. As you may see in the code, I tried setIsRecyclable but I would get errors saying unmatched calls and it wouldn't fix it
You should try to change a couple of things :
move your setters on your ArtistViewHolder object in the setResource function
define your CompactdArtist variable as a final
#Override
public void onBindViewHolder(final ArtistViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder: " + position);
final CompactdArtist current = artists.get(position);
// holder.setIsRecyclable(false);
holder.bindArtist(artists.get(position));
holder.artistImageView.setImageDrawable(null);
try {
current.fetch();
fullRequest.load(new MediaCover(current))
.fallback(ImageUtils.getFallback(mContext))
.into(new ImageViewTarget<Bitmap>(holder.artistImageView) {
#Override
protected void setResource(#Nullable Bitmap resource) {
if (resource == null) {
// holder.setIsRecyclable(true);
return;
}
holder.artistImageView.setImageBitmap(resource);
int color = Palette.from(resource).generate().getMutedColor(0xFFFFFF);
holder.artistBackground.setBackgroundColor(color);
// holder.setIsRecyclable(true);
holder.artistNameText.setText(current.getName());
holder.artistSub.setText(current.getAlbumCount() + " albums");
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
// holder.setIsRecyclable(true);
}
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
holder.artistNameText.setText(R.string.unknown_artist_name);
} catch (IOException e) {
e.printStackTrace();
}
}
I assume that the way you load your images is asynchronous, so in this case your problems resides in the fact that you set your variables as if they were in a synchronous process, that's why you get your information displayed on the wrong blocks.

Recyclerview scrolling is not smooth while updating textview item of recyclerview android

My app recyclerview having audio, imageview, textview as items. When scrolling the recyclerview, it is scrolled properly without any issue if item is not updated. But scrolling the recyclerview is not smooth while playing a audio item of recyclerview, it behaves as below attached gif image. While playing an audio, i am updating audio current playing time in textview. I am not sure what is issue is here. Kindly help me. Thanks.
Here is my code.
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<SessionHistoryMesssage> messageArrayList;
MediaPlayer mPlayer = null;
LocalService mService;
boolean mBound = false;
private RelativeLayout.LayoutParams params;
int currentlyPlayingPosition = -1;
public String mBasePath = "", imageDirectory = "", mSessionId = "", audioDirectory = "";
private ProgressDialog progressDialog;
public RetrofitRestAdapter retrofitRestAdapter;
private MessageAdapter messageListAdapter;
private SessionHistoryResponse sessionHistoryResponse;
public Context mContext;
private int mSelectionPosition = -1;
private int num = 0;
public boolean isSessionStarted=false;
public static Handler mHandler;
public int playposition = -1, sessionstartPosition = -1, alreadystartPosition = -1, sessionendPosition = -1;
public MessageAdapter(Context mContext
, ArrayList<SessionHistoryMesssage> messageArrayList, String mBasePath) {
this.mContext = mContext;
this.messageArrayList = messageArrayList;
this.mBasePath = mBasePath;
mHandler = new Handler();
Intent intent = new Intent(mContext, LocalService.class);
this.mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
// 1. Type a) SEND b) RECEIVE
// 2. Message a) AUDIO b) IMAGE
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
Log.i("ViewType", messageArrayList.get(position).getmMediaType() + "");
Log.i("sent by", messageArrayList.get(position).getmSentBy() + "");
return Integer.parseInt(messageArrayList.get(position).getmSentBy());
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
VHItemAudio dataObjectHolder = null;
if (Integer.parseInt(Utility.readPatientId(mContext)) == viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_a_conversation_item, parent, false);
dataObjectHolder = new VHItemAudio(view);
return dataObjectHolder;
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_b_conversation_item, parent, false);
dataObjectHolder = new VHItemAudio(view);
return dataObjectHolder;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
final VHItemAudio vhItemHolder = (VHItemAudio) holder;
vhItemHolder.bind(messageArrayList.get(position), position);
}
public class VHItemAudio extends RecyclerView.ViewHolder {
ImageView playPauseImageView, imgDownloadAudio, imgDownloadImage;
TextView txtImageSendTime, runningTimerTextView, txtAudioSendTime, txtSessionStart;
SeekBar seekBar;
ImageView imageView;
RelativeLayout layoutAudio, layoutImage, layoutdownloadImage, layoutSessionStart;
ProgressBar progressImageDownload, progressAudioDownload;
private ImageView imageShareImageView,recordAudioImageView,imgEndSession;
public VHItemAudio(View itemView) {
super(itemView);
playPauseImageView = (ImageView) itemView.findViewById(R.id.playPauseImageView);
txtImageSendTime = (TextView) itemView.findViewById(R.id.txtImageSendTime);
runningTimerTextView = (TextView) itemView.findViewById(R.id.runningTimerTextView);
seekBar = (SeekBar) itemView.findViewById(R.id.seekBar);
imgDownloadAudio = (ImageView) itemView.findViewById(R.id.imgDownloadAudio);
imgDownloadImage = (ImageView) itemView.findViewById(R.id.imgDownloadImage);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
txtAudioSendTime = (TextView) itemView.findViewById(R.id.txtAudioSendTime);
layoutAudio = (RelativeLayout) itemView.findViewById(R.id.layoutAudio);
layoutImage = (RelativeLayout) itemView.findViewById(R.id.layoutImage);
layoutdownloadImage = (RelativeLayout) itemView.findViewById(R.id.layoutdownloadImage);
progressImageDownload = (ProgressBar) itemView.findViewById(R.id.progressImageDownload);
progressAudioDownload = (ProgressBar) itemView.findViewById(R.id.progressAudioDownload);
layoutSessionStart = (RelativeLayout) itemView.findViewById(R.id.layoutSessionStart);
txtSessionStart = (TextView) itemView.findViewById(R.id.txtSessionStart);
Log.i("", "Adding Listener");
imageShareImageView= (ImageView) ((PatientActivity)PatientActivity.patientContext).findViewById(R.id.imageShareImageView);
recordAudioImageView= (ImageView) ((PatientActivity) PatientActivity.patientContext).findViewById(R.id.recordAudioImageView);
imgEndSession= (ImageView) ((PatientActivity) PatientActivity.patientContext).findViewById(R.id.imgEndSession);
}
public void bind(final SessionHistoryMesssage sessionHistoryMesssage, final int position) {
if (sessionHistoryMesssage.getmSessionStart().equalsIgnoreCase("yes")
&& Integer.parseInt(sessionHistoryMesssage.getmTransToId())
== Integer.parseInt(Utility.readPatientId(mContext))) {
sessionstartPosition = position;
isSessionStarted=true;
} else if (sessionHistoryMesssage.getmSessionStart().equalsIgnoreCase("already started")) {
alreadystartPosition = position;
} else if (sessionHistoryMesssage.getmSessionStart().equalsIgnoreCase("end")) {
sessionendPosition = position;
} else {
}
if (sessionHistoryMesssage.getmMediaType().equalsIgnoreCase("audio")) {
layoutAudio.setVisibility(View.VISIBLE);
layoutImage.setVisibility(View.GONE);
seekBar.setFocusable(false);
txtAudioSendTime.setText(Utility.convertDateTime(sessionHistoryMesssage.getmSentTime()));
audioDirectory = Environment.getExternalStorageDirectory().getAbsolutePath() + "/A/Audio/";
File file = new File(Environment.getExternalStorageDirectory()
+ "/A/Audio/" + messageArrayList.get(position).getmMediaValue());
if (file.exists()) {
imgDownloadAudio.setVisibility(View.GONE);
playPauseImageView.setVisibility(View.VISIBLE);
playPauseImageView.setImageResource(R.drawable.play_icon);
} else {
imgDownloadAudio.setVisibility(View.VISIBLE);
playPauseImageView.setVisibility(View.GONE);
imgDownloadAudio.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
imgDownloadAudio.setVisibility(View.GONE);
Log.v("basepath", mBasePath);
new DownloadFile(mBasePath + messageArrayList.get(position).getmMediaPath() +
messageArrayList.get(position).getmMediaValue(),
audioDirectory + messageArrayList.get(position).getmMediaValue()
, imgDownloadAudio, progressAudioDownload).execute();
}
});
}
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (playposition == position) {
int seekBarPosition = seekBar.getProgress();
mPlayer.seekTo(seekBarPosition);
}
}
});
playPauseImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!mService.isPlaying()) {
playposition = position;
mService.playAudio(audioDirectory + messageArrayList.get(position).getmMediaValue());
notifyDataSetChanged();
} else {
if (playposition == position) {
playposition = -1;
mHandler.removeCallbacks(null);
mService.stopAudio();
notifyDataSetChanged();
} else {
playposition = position;
mService.playAudio(audioDirectory + messageArrayList.get(position).getmMediaValue());
notifyDataSetChanged();
}
}
}
});
} else if (messageArrayList.get(position).getmMediaType().equalsIgnoreCase("photo")) {
layoutImage.setVisibility(View.VISIBLE);
layoutAudio.setVisibility(View.GONE);
txtImageSendTime.setText(Utility.convertDateTime(messageArrayList.get(position).getmSentTime()));
imageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath() + "/A/Image/";
Log.v("image path123", mBasePath + messageArrayList.get(position).getmMediaPath()
+ messageArrayList.get(position).getmMediaValue());
try {
File file = new File(Environment.getExternalStorageDirectory()
+ "/A/Image/" + messageArrayList.get(position).getmMediaValue());
if (!file.exists()) {
layoutdownloadImage.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
} else {
layoutdownloadImage.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
imageView.setImageBitmap(BitmapFactory.decodeFile(file.getAbsolutePath()));
}
layoutdownloadImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mSessionId = messageArrayList.get(position).getmSessionId();
imgDownloadImage.setVisibility(View.GONE);
new DownloadImage(mBasePath + messageArrayList.get(position).getmMediaPath()
+ sessionHistoryMesssage.getmMediaValue(),
sessionHistoryMesssage.getmMediaValue(),
imgDownloadImage, progressImageDownload
).execute();
}
});
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showImageDialog(position);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
if (sessionstartPosition == position) {
layoutSessionStart.setVisibility(View.VISIBLE);
layoutAudio.setVisibility(View.GONE);
layoutImage.setVisibility(View.GONE);
txtSessionStart.setText("Start session");
if (sessionHistoryMesssage.getmMediaType().equalsIgnoreCase("audio")) {
params = (RelativeLayout.LayoutParams) layoutSessionStart.getLayoutParams();
params.addRule(RelativeLayout.BELOW, R.id.layoutAudio);
} else if (sessionHistoryMesssage.getmMediaType().equalsIgnoreCase("photo")) {
params = (RelativeLayout.LayoutParams) layoutSessionStart.getLayoutParams();
params.addRule(RelativeLayout.BELOW, R.id.layoutImage);
} else {
}
if(!isSessionStarted) {
imageShareImageView.setVisibility(View.GONE);
recordAudioImageView.setVisibility(View.GONE);
imgEndSession.setVisibility(View.GONE);
isSessionStarted=true;
}
txtSessionStart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
retrofitRestAdapter = new RetrofitRestAdapter();
Map<String, String> sessionstartParams = new HashMap<String, String>();
sessionstartParams.put("session_id", sessionHistoryMesssage.getmSessionId());
final ProgressDialog progressDialog = new ProgressDialog(mContext);
progressDialog.setTitle("Please wait...");
progressDialog.show();
retrofitRestAdapter.sessionStart(sessionstartParams, new Callback<SessionHistoryResponse>() {
#Override
public void onResponse(Call<SessionHistoryResponse> call, Response<SessionHistoryResponse> response) {
Log.v("session start response", new Gson().toJson(response.body()));
progressDialog.dismiss();
txtSessionStart.setEnabled(false);
txtSessionStart.setClickable(false);
imageShareImageView.setVisibility(View.VISIBLE);
recordAudioImageView.setVisibility(View.VISIBLE);
imgEndSession.setVisibility(View.VISIBLE);
}
#Override
public void onFailure(Call<SessionHistoryResponse> call, Throwable t) {
t.printStackTrace();
progressDialog.dismiss();
imageShareImageView.setVisibility(View.GONE);
recordAudioImageView.setVisibility(View.GONE);
imgEndSession.setVisibility(View.GONE);
}
});
}
});
} else {
}
if (position == alreadystartPosition) {
layoutSessionStart.setClickable(false);
txtSessionStart.setClickable(false);
imageShareImageView.setVisibility(View.VISIBLE);
recordAudioImageView.setVisibility(View.VISIBLE);
imgEndSession.setVisibility(View.VISIBLE);
} else {
}
if (position == sessionendPosition) {
layoutSessionStart.setClickable(false);
txtSessionStart.setClickable(false);
// layoutSessionStart.setVisibility(View.VISIBLE);
txtSessionStart.setText("End session");
if (sessionHistoryMesssage.getmMediaType().equalsIgnoreCase("audio")) {
params = (RelativeLayout.LayoutParams) layoutSessionStart.getLayoutParams();
params.addRule(RelativeLayout.BELOW, R.id.layoutAudio);
} else if (sessionHistoryMesssage.getmMediaType().equalsIgnoreCase("photo")) {
params = (RelativeLayout.LayoutParams) layoutSessionStart.getLayoutParams();
params.addRule(RelativeLayout.BELOW, R.id.layoutImage);
} else {
}
imageShareImageView.setVisibility(View.GONE);
recordAudioImageView.setVisibility(View.GONE);
imgEndSession.setVisibility(View.GONE);
} else {
}
if (position == playposition) {
if (mBound) {
Log.v("service", "bound");
mHandler.removeCallbacks(null);
seekBar.setMax(mService.getrunningTime() / 1000);
Log.v("duration", mService.getrunningTime() + "");
playPauseImageView.setImageResource(R.drawable.stop_icon);
((PatientActivity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
if (mService.isPlaying()) {
num = mService.getDuration();
seekBar.setProgress(num / 1000);
runningTimerTextView.setText(convertMilliToMinutes(num));
// vhItemHolder.seekBar.setFocusable(false);
mHandler.postDelayed(this, 50);
} else {
playposition = -1;
mHandler.removeCallbacks(null);
seekBar.setProgress(0);
seekBar.setFocusable(false);
runningTimerTextView.setText("00:00");
playPauseImageView.setImageResource(R.drawable.play_icon);
}
}
});
} else {
Log.v("service", "not bound");
}
} else {
mHandler.removeCallbacks(null);
playPauseImageView.setImageResource(R.drawable.play_icon);
runningTimerTextView.setText("00:00");
seekBar.setMax(0);
}
}
}
Service class
public class LocalService extends Service {
boolean mBound = false;
private final IBinder mBinder = new LocalBinder();
private int timer;
private MediaPlayer mediaPlayer = new MediaPlayer();
private boolean isStopped = true;
// Random number generator
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getrunningTime() {
return mediaPlayer.getDuration();
}
public int getDuration() {
// Log.v("player duration",timer+"");
if (mediaPlayer != null) {
if (!isStopped) {
Log.v("playing", (mediaPlayer.getCurrentPosition()) + "");
return mediaPlayer.getCurrentPosition();
} else {
// mediaPlayer.reset();
mHandler.removeCallbacks(null);
return 0;
}
} else {
Log.v("not playing", (mediaPlayer.getCurrentPosition()) + "");
mHandler.removeCallbacks(null);
return 0;
}
}
#Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
isStopped = false;
//playAudio();
Log.v("max duration", timer + "");
}
});
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
//mediaPlayer.stop();
isStopped = true;
mediaPlayer.reset();
//mediaPlayer.release();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isPlaying() {
if (mediaPlayer != null) {
try {
if (mediaPlayer.isPlaying()) {
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
} else {
return false;
}
}
public void playAudio(String path) {
Log.v("audio path",path);
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
mediaPlayer.reset();
try {
mediaPlayer.setDataSource(path);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopAudio() {
if (mediaPlayer != null) {
mediaPlayer.stop();
// mediaPlayer.reset();
isStopped = true;
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
mediaPlayer.reset();
mediaPlayer.release();
}
#Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.reset();
mediaPlayer.release();
}
}
setHasStableIds(true); Add this in your adapter constructor and Override these two methodes in adapter.
#Override
public long getItemId(int pos) {
return pos;
}
#Override
public int getItemViewType(int pos) {
//return pos;
return Integer.parseInt(messageArrayList.get(position).getmSentBy());
}
It might help you...
Because you are calling notifyDataSetChanged() and it's updating the whole recyclerview whenever you do that. You better call notifyItemChanged(position) to update the certain item that you are supposed to update.
((PatientActivity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
if (mService.isPlaying()) {
num = mService.getDuration();
seekBar.setProgress(num / 1000);
runningTimerTextView.setText(convertMilliToMinutes(num));
// vhItemHolder.seekBar.setFocusable(false);
mHandler.postDelayed(this, 50);
} else {
playposition = -1;
mHandler.removeCallbacks(null);
seekBar.setProgress(0);
seekBar.setFocusable(false);
runningTimerTextView.setText("00:00");
playPauseImageView.setImageResource(R.drawable.play_icon);
}
}
});
can you convert that into something like.
if (position == playposition) {
if (mBound) {
Log.v("service", "bound");
mHandler.removeCallbacks(null);
seekBar.setMax(mService.getrunningTime() / 1000);
Log.v("duration", mService.getrunningTime() + "");
playPauseImageView.setImageResource(R.drawable.stop_icon);
if (mService.isPlaying()) {
num = mService.getDuration();
seekBar.setProgress(num / 1000);
runningTimerTextView.setText(convertMilliToMinutes(num));
// vhItemHolder.seekBar.setFocusable(false);
notifyItemChanged(position);
} else {
playposition = -1;
mHandler.removeCallbacks(null);
seekBar.setProgress(0);
seekBar.setFocusable(false);
runningTimerTextView.setText("00:00");
playPauseImageView.setImageResource(R.drawable.play_icon);
notifyItemChanged(position);
}
}
}
Try to remove the whole usage of the runnable.

video & image viewPager

I have a problem in my code can anyone help me?
I need to make app play story like Messenger app
I want to display a list of images and videos in viewpager automatically or manually
I do it but I faced some problem
First:
when page of image is view and the next page play video the sound of video is playing when view the image because the pager load the next fragment i used
mViewPager.setOffscreenPageLimit(0); but i didn't do any thing
Second one:
when I move manually from video to image the video not stop playing when I search for a solution I found this:
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (this.isVisible())
{
if (!isVisibleToUser) // If we are becoming invisible, then...
{
//player.release();}}}}
it's stop the player but if there is a video in next page it doesn't play
How can I solve this problem?
My code:
public class PostsViews extends Activity {
ArrayList<Post> postsList;
Post postView;
Debate debate;
int position;
private ViewPager mViewPager;
private DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;
private Timer t = new Timer();
private int mCurrentPagerIndex = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_posts_views);
position = 0;
ArrayList postList = new ArrayList<>();
postList.add(new Post("debate_112254", "post_123", 1, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FImages%2FPhoto_96uMAYBw8b.jpg?alt=media&token=71a5fb9d-7a71-465b-b4e3-4ad2c7513bc5"));
postList.add(new Post("debate_112254", "post_111", 2, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FVideo%2FVideo_ZVZFgP5JGT.mp4?alt=media&token=07ecb08f-950b-46e3-86aa-ea72e6ccf8d5"));
postList.add(new Post("debate_112254", "post_1563", 2, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FImages%2FPhoto_mWB8XRF7m5.jpg?alt=media&token=85ca502a-09df-46ab-9b0f-218e28487513"));
postList.add(new Post("debate_112254", "post_189", 1, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FImages%2FPhoto_qVxVgeqUmR.jpg?alt=media&token=8c079e15-4579-41ee-b373-f34dc23ad045"));
postList.add(new Post("debate_112254", "post_561", 1, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FImages%2FPhoto_L4wKupgboV.jpg?alt=media&token=3f523167-426c-43bd-91df-b146ee8633db"));
postList.add(new Post("debate_112254", "post_56221", 2, "https://firebasestorage.googleapis.com/v0/b/satol-19f92.appspot.com/o/UsersPost%2FVideo%2FVideo_qS6dMVjMv0.mp4?alt=media&token=fca08c17-b4bb-46eb-a698-eef1f3df3980"));
debate = new Debate("debate_112254", postList, "2017-07-12 12:30");
postsList = debate.getPost();
Log.i("-----", debate.getKey());
mDemoCollectionPagerAdapter = new DemoCollectionPagerAdapter(getFragmentManager(), debate);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mDemoCollectionPagerAdapter);
mViewPager.setCurrentItem(position);
mViewPager.setOffscreenPageLimit(0);
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
t.cancel();
t = new Timer();
snapImage();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
snapImage();
}
public void snapImage() {
final int pos;
pos = mViewPager.getCurrentItem();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if (debate.getPost().get(mViewPager.getCurrentItem()).getPostType() == 1) {
if (pos == debate.getPost().size() - 1) {
try {
t.cancel();
finish();
} catch (Exception e) {
}
} else {
runOnUiThread(new Runnable() {
#Override
public void run() {
mViewPager.setCurrentItem(pos + 1);
}
});
}
}
}
}, 10000, 10000);
}
#Override
public void onPause() {
super.onPause();
try {
if (t != null)
t.cancel();
} catch (Exception e) {
}
}
}
public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
Depate depate;
public DemoCollectionPagerAdapter(FragmentManager fm, Depate depate) {
super(fm);
this.depate=depate;
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new DemoObjectFragment();
Bundle args = new Bundle();
// Our object is just an integer :-P
args.putInt(DemoObjectFragment.ARG_OBJECT, i );
args.putSerializable("depate",depate);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return depate.getPost().size();
}
#Override
public CharSequence getPageTitle(int position) {
return "OBJECT " + (position + 1);
}
}
DemoObjectFragment.class
public class DemoObjectFragment extends Fragment implements SurfaceHolder.Callback,
MediaPlayer.OnPreparedListener, View.OnClickListener, MediaPlayer.OnCompletionListener {
public static final String ARG_OBJECT = "object";
private Depate depate;
private int position;
SurfaceView videoSurface;
MediaPlayer player;
VideoControllerView controller;
private ImageView postImage;
private Post postView;
private String filePath;
private String type;
private FrameLayout frameLayout;
private RelativeLayout rl_video;
private RelativeLayout activity_view_post;
private int mVideoWidth, mVideoHeight;
private Activity activity;
private String fileName;
private ProgressBar loading;
private LinearLayout ll_data;
private ImageView thumb;
private long POST_VIEW_COUNT = 1;
private String depateID;
private String postID;
long time;
private ProgressDialog pd;
private ArrayList<String> debateList = new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View viewLayout = inflater.inflate(R.layout.fragment_demo_object, container, false);
Bundle args = getArguments();
depate = (Depate) (args.getSerializable("depate"));
position = args.getInt(ARG_OBJECT);
postView = depate.getPost().get(position);
activity = getActivity();
frameLayout = (FrameLayout) viewLayout.findViewById(R.id.videoSurfaceContainer);
rl_video = (RelativeLayout) viewLayout.findViewById(R.id.rl_video);
postImage = (ImageView) viewLayout.findViewById(R.id.postImage);
thumb = (ImageView) viewLayout.findViewById(R.id.thumb);
ll_data = (LinearLayout) viewLayout.findViewById(R.id.ll_data);
loading = (ProgressBar) viewLayout.findViewById(R.id.loading);
loading.getIndeterminateDrawable().setColorFilter(Color.parseColor("#6782fa"), android.graphics.PorterDuff.Mode.MULTIPLY);
loading.setVisibility(View.VISIBLE);
videoSurface = (SurfaceView) viewLayout.findViewById(R.id.videoSurface);
fillData(postView, depate);
return viewLayout;
}
private void fillData(final Post postView, Depate depate) {
filePath = postView.getFilePath();
type = (postView.getPostType() == 1 ? "image/jpeg" : "video/mp4");
if (type.equalsIgnoreCase("video/mp4")) {
SurfaceHolder videoHolder = videoSurface.getHolder();
videoHolder.addCallback(this);
player = new MediaPlayer();
try {
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(activity, Uri.parse(filePath));
player.setOnPreparedListener(this);
player.setOnCompletionListener(this);
thumb.setVisibility(View.VISIBLE);
loading.setVisibility(View.VISIBLE);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
rl_video.setVisibility(View.VISIBLE);
postImage.setVisibility(View.GONE);
} else {
thumb.setVisibility(View.GONE);
postImage.setVisibility(View.VISIBLE);
rl_video.setVisibility(View.GONE);
loading.setVisibility(View.VISIBLE);
Glide.with(activity)
.load(filePath).centerCrop().listener(new RequestListener<String, GlideDrawable>() {
#Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
postImage.setScaleType(ImageView.ScaleType.CENTER);
postImage.setImageResource(R.drawable.ic_avatar);
loading.setVisibility(View.GONE);
return false;
}
#Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
loading.setVisibility(View.GONE);
return false;
}
}).into(postImage);
}
}
#Override
public void onPrepared(MediaPlayer mp) {
player.start();
loading.setVisibility(View.GONE);
thumb.setVisibility(View.GONE);
}
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
try {
final ViewPager viewPager = (ViewPager) getActivity().findViewById(R.id.pager);
final int pos;
position = viewPager.getCurrentItem();
if (position == depate.getPost().size() - 1) {
try {
getActivity().finish();
} catch (Exception e) {
}
} else {
pos = position;
viewPager.setCurrentItem(pos + 1);
}
} catch (Exception e) {
}
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (this.isVisible()) {
if (!isVisibleToUser) {
if (player != null) {
player.release();
}
}else player.start();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
player.setDisplay(holder);
player.prepareAsync();
} catch (Exception e) {
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
activity_posts_views.xml
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#000000" />
fragment_demo_object.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_view_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000"
android:fitsSystemWindows="true"
android:orientation="vertical">
<RelativeLayout
android:id="#+id/rl_media"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000">
<RelativeLayout
android:id="#+id/rl_video"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<FrameLayout
android:id="#+id/videoSurfaceContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/videoSurface"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
<ImageView
android:id="#+id/thumb"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="#drawable/white_circle_opacity"
android:padding="5dp"
android:src="#drawable/evp_action_play" />
</RelativeLayout>
<ImageView
android:id="#+id/postImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#000"
android:visibility="visible" />
<ProgressBar
android:id="#+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
</RelativeLayout>
</RelativeLayout>
Debate.class
public class Debate implements Serializable {
String CreatedAt;
String key;
ArrayList<Post> Post;
public Debate(String key, ArrayList<Post> posts, String createdAt) {
this.key = key;
this.Post = posts;
this.CreatedAt = createdAt;
}
public ArrayList<Post> getPost() {
return Post;
}
public void setPost(ArrayList<Post> Post) {
this.Post = Post;
}
public String getCreatedAt() {
return CreatedAt;
}
public void setCreatedAt(String CreatedAt) {
this. CreatedAt = CreatedAt;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
Post.class
public class Post implements Serializable {
String DebateId;
String PostId;
int PostType;
String FilePath;
public Post(String debateId, String postId, int postType, String filePath) {
DebateId = debateId;
PostId = postId;
PostType = postType;
FilePath = filePath;
}
public String getPostId() {
return PostId;
}
public void setPostId(String postId) {
PostId = postId;
}
public String getDebateId() {
return DebateId;
}
public void setDebateId(String DebateId) {
this.DebateId = DebateId;
}
public int getPostType() {
return PostType;
}
public void setPostType(int PostType) {
this.PostType = PostType;
}
public String getFilePath() {
return FilePath;
}
public void setFilePath(String FilePath) {
this.FilePath = FilePath;
}
}
You can write your custom viewpager by copy the viewpager's source like below sample class:
https://gist.github.com/anonymous/24831ea667540ab6a43cc777b7e38a65
Let me know if it works for you.

Streaming video list using Exoplayer

I'd like to implement streaming video list app. I used a RecyclerView to display my list item. Item type includes 4 type: Article, Status, Photo and Video. We should only focus on Video type. Here is my code for RecyclerView's adapter:
public class FollowedPostAdapter extends RecyclerView.Adapter implements OnFollowTagCallback, OnLikeCallback {
private Context context;
private List<PostItem> newsFeedList;
public RecyclerView recyclerView;
public LinearLayoutManager linearLayoutManager;
private HashMap<Integer, DemoPlayer> playerList;
private int visibleThreshold = 5;
// private int previousTotal = 0;
private int visibleItemCount, firstVisibleItem, totalItemCount;
private boolean loading;
private OnRecyclerViewLoadMoreListener loadMoreListener;
private final String text_comment;
private final String mReadMoreHtml;
private long userId;
public FollowedPostAdapter(Context context, RecyclerView recyclerView, List<PostItem> newsFeedList) {
this.context = context;
playerList = new HashMap<>();
this.newsFeedList = newsFeedList;
this.recyclerView = recyclerView;
this.linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if (viewType == Constants.VIEWTYPE_ARTICLE) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_article_item, parent, false);
viewHolder = new ArticleViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_STATUS) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_status_item, parent, false);
viewHolder = new StatusViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_PHOTO) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_photo_item, parent, false);
viewHolder = new PhotoViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_VIDEO) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_video_item, parent, false);
viewHolder = new VideoViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.progressbar_item, parent, false);
viewHolder = new ProgressBarViewHolder(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ArticleViewHolder) {
// code
} else if (holder instanceof StatusViewHolder) {
// code
} else if (holder instanceof PhotoViewHolder) {
// code
} else if (holder instanceof VideoViewHolder) {
PostItem item = newsFeedList.get(position);
VideoViewHolder mHolder = (VideoViewHolder) holder;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mHolder.videoView.getLayoutParams();
lp.height = (int) (ScreenHelper.getScreenWidth((Activity) context) * Constants.POST_IMAGE_RATIO);
mHolder.videoView.setLayoutParams(lp);
mHolder.setup(item);
Picasso.with(context).load(item.imageCover).error(R.drawable.ic_user_avatar).placeholder(R.drawable.ic_user_avatar).into(mHolder.iv_avatar);
if (item.tags != null && item.tags.size() > 0) {
// get first tag as main tag
generateTagViews(mHolder.tag_flow_layout, item.tags.subList(0, 1), position);
mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
// mHolder.indicator.setVisibility(View.VISIBLE);
} else {
mHolder.tag_flow_layout.setVisibility(View.GONE);
// mHolder.indicator.setVisibility(View.GONE);
}
if (item.time_created != null) {
mHolder.tv_time.setText(item.time_created);
//mHolder.indicator.setVisibility(View.VISIBLE);
mHolder.tv_time.setVisibility(View.VISIBLE);
} else {
//mHolder.indicator.setVisibility(View.GONE);
mHolder.tv_time.setVisibility(View.GONE);
}
if (item.description_short.isEmpty())
mHolder.tv_description.setVisibility(View.GONE);
else {
mHolder.tv_description.setText(item.description_short);
mHolder.tv_description.setVisibility(View.VISIBLE);
}
mHolder.btn_comment.setText(String.valueOf(item.count_comment));
mHolder.btn_like.setText(String.valueOf(item.count_like));
mHolder.btn_unlike.setText(String.valueOf(item.count_unlike));
mHolder.btn_share.setText(String.valueOf(item.count_share));
if (item.tags.size() != 0) {
int tagId = item.tags.get(0).tag_id;
setFollowButtonActive(mHolder.btn_follow, TagHelper.isTagFollowed(tagId));
} else
setFollowButtonActive(mHolder.btn_follow, false);
}
}
#Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
if (holder instanceof VideoViewHolder) {
DemoPlayer player = playerList.get(holder.getAdapterPosition());
if (player != null) {
player.release();
playerList.remove(holder.getAdapterPosition());
}
}
}
public void pauseAllPlayers() {
for (int i = 0; i <= newsFeedList.size(); i++) {
DemoPlayer player = playerList.get(i);
if (player != null) {
if (player.getPlayerControl().isPlaying())
player.getPlayerControl().pause();
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForLayoutPosition(i);
if (holder != null && holder instanceof VideoViewHolder) {
((VideoViewHolder) holder).btn_play.setVisibility(View.VISIBLE);
}
}
}
}
public void refreshData() {
notifyDataSetChanged();
}
public class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
//
}
public class StatusViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// code
}
public class PhotoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// code
}
public class VideoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, SurfaceHolder.Callback, AudioCapabilitiesReceiver.Listener, DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener {
#Bind(R.id.iv_avatar)
ImageView iv_avatar;
#Bind(R.id.tag_flow_layout)
FlowLayout tag_flow_layout;
#Bind(R.id.tv_time)
TextView tv_time;
#Bind(R.id.btn_follow)
FancyButton btn_follow;
#Bind(R.id.btn_comment)
FancyButton btn_comment;
#Bind(R.id.btn_like)
FancyButton btn_like;
#Bind(R.id.btn_unlike)
FancyButton btn_unlike;
#Bind(R.id.btn_share)
FancyButton btn_share;
#Bind(R.id.root)
FrameLayout videoView;
#Bind(R.id.btn_play)
ImageView btn_play;
#Bind(R.id.tv_description)
TextView tv_description;
// player's variable
private EventLogger eventLogger;
// private VideoControllerView mediaController;
private View shutterView;
private AspectRatioFrameLayout videoFrame;
private SurfaceView surfaceView;
private SubtitleLayout subtitleLayout;
private DemoPlayer player;
private boolean playerNeedsPrepare;
private long playerPosition = 0;
private Uri contentUri;
private int contentType;
private String contentId;
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_OTHER = 3;
private static final String EXT_DASH = ".mpd";
private static final String EXT_SS = ".ism";
private static final String EXT_HLS = ".m3u8";
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
public VideoViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
iv_avatar.setOnClickListener(this);
btn_follow.setOnClickListener(this);
btn_comment.setOnClickListener(this);
btn_like.setOnClickListener(this);
btn_unlike.setOnClickListener(this);
btn_share.setOnClickListener(this);
// player's setup
View root = view.findViewById(R.id.root);
root.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
doPlayResume();
}
});
// root.setOnTouchListener(new View.OnTouchListener() {
// #Override
// public boolean onTouch(View view, MotionEvent motionEvent) {
// if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
// toggleControlsVisibility();
// } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
// view.performClick();
// }
// return true;
// }
// });
// root.setOnKeyListener(new View.OnKeyListener() {
// #Override
// public boolean onKey(View v, int keyCode, KeyEvent event) {
// if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
// || keyCode == KeyEvent.KEYCODE_MENU) {
// return false;
// }
// return mediaController.dispatchKeyEvent(event);
// }
// });
shutterView = view.findViewById(R.id.shutter);
videoFrame = (AspectRatioFrameLayout) view.findViewById(R.id.video_frame);
surfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
subtitleLayout = (SubtitleLayout) view.findViewById(R.id.subtitles);
// mediaController = new VideoControllerView(context);
// mediaController.setAnchorView((ViewGroup) root);
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(context, this);
audioCapabilitiesReceiver.register();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_avatar:
viewTagDetail(getAdapterPosition());
break;
case R.id.btn_follow:
followTag((FancyButton) v, getAdapterPosition());
break;
case R.id.btn_comment:
commentPost(getAdapterPosition());
break;
case R.id.btn_like:
likePost(getAdapterPosition(), btn_like, btn_unlike);
break;
case R.id.btn_unlike:
unlikePost(getAdapterPosition(), btn_like, btn_unlike);
break;
case R.id.btn_share:
sharePost(getAdapterPosition());
break;
case R.id.btn_play:
doPlayResume();
break;
}
}
public void setup(PostItem item) {
releasePlayer();
player = playerList.get(getAdapterPosition());
contentUri = Uri.parse(item.link);
contentType = TYPE_OTHER;
contentId = String.valueOf(item.post_id);
configureSubtitleView();
if (player == null) {
preparePlayer(false);
} else {
player.setBackgrounded(false);
}
}
// public void saveCurrentPosition() {
// if (player != null)
// videoItemList.get(getAdapterPosition()).position = player.getCurrentPosition();
// }
public void doPlayResume() {
if (player == null) {
return;
}
if (player.getPlayerControl().isPlaying()) {
player.getPlayerControl().pause();
} else {
player.getPlayerControl().start();
}
showControls();
}
#Override
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
if (player == null) {
return;
}
boolean backgrounded = player.getBackgrounded();
boolean playWhenReady = player.getPlayWhenReady();
releasePlayer();
preparePlayer(playWhenReady);
player.setBackgrounded(backgrounded);
}
private DemoPlayer.RendererBuilder getRendererBuilder() {
String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
switch (contentType) {
case TYPE_SS:
return new SmoothStreamingRendererBuilder(context, userAgent, contentUri.toString(),
new SmoothStreamingTestMediaDrmCallback());
case TYPE_DASH:
return new DashRendererBuilder(context, userAgent, contentUri.toString(),
new WidevineTestMediaDrmCallback(contentId));
case TYPE_HLS:
return new HlsRendererBuilder(context, userAgent, contentUri.toString());
case TYPE_OTHER:
return new ExtractorRendererBuilder(context, userAgent, contentUri);
default:
throw new IllegalStateException("Unsupported type: " + contentType);
}
}
private void preparePlayer(boolean playWhenReady) {
if (player == null) {
player = new DemoPlayer(getRendererBuilder());
playerList.put(getAdapterPosition(), player);
player.addListener(this);
player.setCaptionListener(this);
player.setMetadataListener(this);
player.seekTo(playerPosition);
playerNeedsPrepare = true;
// mediaController.setMediaPlayer(player.getPlayerControl());
// mediaController.setEnabled(true);
eventLogger = new EventLogger();
eventLogger.startSession();
player.addListener(eventLogger);
player.setInfoListener(eventLogger);
player.setInternalErrorListener(eventLogger);
}
if (playerNeedsPrepare) {
player.prepare();
playerNeedsPrepare = false;
}
player.setSurface(surfaceView.getHolder().getSurface());
player.setPlayWhenReady(playWhenReady);
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
eventLogger.endSession();
eventLogger = null;
btn_play.setVisibility(View.VISIBLE);
}
}
#Override
public void onStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
showControls();
}
}
#Override
public void onError(Exception e) {
if (e instanceof UnsupportedDrmException) {
// Special case DRM failures.
UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
int stringId = Util.SDK_INT < 18 ? R.string.drm_error_not_supported
: unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.drm_error_unsupported_scheme : R.string.drm_error_unknown;
Toast.makeText(context, stringId, Toast.LENGTH_LONG).show();
}
playerNeedsPrepare = true;
showControls();
}
#Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
shutterView.setVisibility(View.GONE);
videoFrame.setAspectRatio(
height == 0 ? 1 : (width * pixelWidthHeightRatio) / height);
}
private boolean haveTracks(int type) {
return player != null && player.getTrackCount(type) > 0;
}
// private void toggleControlsVisibility() {
// if (mediaController.isShowing()) {
// mediaController.hide();
// } else {
// showControls();
// }
// }
private void showControls() {
// mediaController.show(5000);
if (player.getPlayerControl().isPlaying())
btn_play.setVisibility(View.GONE);
else
btn_play.setVisibility(View.VISIBLE);
}
#Override
public void onCues(List<Cue> cues) {
subtitleLayout.setCues(cues);
}
#Override
public void onId3Metadata(Map<String, Object> metadata) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (player != null) {
player.setSurface(holder.getSurface());
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (player != null) {
holder.lockCanvas();
player.blockingClearSurface();
}
}
private void configureSubtitleView() {
CaptionStyleCompat style;
float fontScale;
if (Util.SDK_INT >= 19) {
style = getUserCaptionStyleV19();
fontScale = getUserCaptionFontScaleV19();
} else {
style = CaptionStyleCompat.DEFAULT;
fontScale = 1.0f;
}
subtitleLayout.setStyle(style);
subtitleLayout.setFractionalTextSize(SubtitleLayout.DEFAULT_TEXT_SIZE_FRACTION * fontScale);
}
#TargetApi(19)
private float getUserCaptionFontScaleV19() {
CaptioningManager captioningManager =
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
return captioningManager.getFontScale();
}
#TargetApi(19)
private CaptionStyleCompat getUserCaptionStyleV19() {
CaptioningManager captioningManager =
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
private int inferContentType(Uri uri, String fileExtension) {
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
: uri.getLastPathSegment();
if (lastPathSegment == null) {
return TYPE_OTHER;
} else if (lastPathSegment.endsWith(EXT_DASH)) {
return TYPE_DASH;
} else if (lastPathSegment.endsWith(EXT_SS)) {
return TYPE_SS;
} else if (lastPathSegment.endsWith(EXT_HLS)) {
return TYPE_HLS;
} else {
return TYPE_OTHER;
}
}
}
public class ProgressBarViewHolder extends RecyclerView.ViewHolder {
public ProgressBarViewHolder(View view) {
super(view);
}
}
public void unregisterEventBus() {
EventBus.getDefault().unregister(this);
}
}
When item type is video, I create an ExoPlayer instance to play this video and add this instance to a HashMap for later use (with key value is the item's position). If a video item is being recycled, I release the player and remove it from HashMap.
Everything looks good but a problem. I suppose the video item is at 0 position (now we can see the video preview in this item). I scroll down the RecyclerView just enough to hide the item 0. At this moment, the VideoViewHolder of item 0 is not recycled yet. Then, I scroll up to reveal item 0. The view is now blank, not show the video preview. I click item 0 to play the video, the player only plays audio (sound), not show video. After playing a few seconds, the video is now visible.
I debugged and found that after I scrolled down to hide video item, the SurfaceView is destroyed. When scroll up to reveal video item, the SurfaceView is created. I think this is the reason why VideoViewHolder shows blank view instead of the video preview.
Here is my question: How to display video preview after scroll back to video item?
Here is note from ExoPlayer documentation
SurfaceView rendering wasn’t properly synchronized with view animations until Android N. On earlier releases this could result in unwanted effects when a SurfaceView was placed into scrolling container, or when it was subjected to animation. Such effects included the SurfaceView’s contents appearing to lag slightly behind where it should be displayed, and the view turning black when subjected to animation.
To achieve smooth animation or scrolling of video prior to Android N, it’s therefore necessary to use TextureView rather than SurfaceView. If smooth animation or scrolling is not required then SurfaceView should be preferred
To enable TextureView surface_type you have to set it in your xml file like shown below
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="#+id/playerView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:surface_type="texture_view"/>

Categories

Resources