Last active
June 18, 2022 12:05
-
-
Save shubhubhosale/9ef8f34a00ee41835d909c1a023b3f7a to your computer and use it in GitHub Desktop.
Android AutoPlay Videos in RecyclerView like Instagram using SimpleVideoView and
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<LinearLayout 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:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context="pune.university.engineering.gifutilstest.VideoListActivity"> | |
<android.support.v7.widget.RecyclerView | |
android:id="@+id/recyclerView" | |
android:layout_width="368dp" | |
android:layout_height="495dp" | |
tools:layout_editor_absoluteX="8dp" | |
tools:layout_editor_absoluteY="8dp" /> | |
</LinearLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: 'com.android.application' | |
android { | |
compileSdkVersion 26 | |
buildToolsVersion "27.0.3" | |
defaultConfig { | |
applicationId "pune.university.engineering.gifutilstest" | |
minSdkVersion 17 | |
targetSdkVersion 26 | |
versionCode 1 | |
versionName "1.0" | |
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |
} | |
buildTypes { | |
release { | |
minifyEnabled false | |
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
} | |
} | |
} | |
dependencies { | |
compile fileTree(include: ['*.jar'], dir: 'libs') | |
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { | |
exclude group: 'com.android.support', module: 'support-annotations' | |
}) | |
compile 'com.android.support:appcompat-v7:26.+' | |
compile 'com.android.support.constraint:constraint-layout:1.0.2' | |
compile 'com.android.support:recyclerview-v7:26.0.0-alpha1' | |
compile 'com.jakewharton:butterknife:5.1.1' | |
compile 'com.squareup.picasso:picasso:2.3.2' | |
compile 'com.klinkerapps:simple_videoview:1.2.4' | |
compile 'com.danikula:videocache:2.7.0' | |
testCompile 'junit:junit:4.12' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package pune.university.engineering.gifutilstest; | |
import android.content.Context; | |
import android.net.Uri; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.Log; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.FrameLayout; | |
import android.widget.ImageView; | |
import android.widget.LinearLayout; | |
import android.widget.TextView; | |
import android.widget.VideoView; | |
import com.facebook.drawee.view.SimpleDraweeView; | |
import com.klinker.android.simple_videoview.SimpleVideoView; | |
import com.squareup.picasso.Picasso; | |
import java.util.ArrayList; | |
import java.util.List; | |
import butterknife.ButterKnife; | |
import butterknife.InjectView; | |
/** | |
* Created by Altair on 5/27/2018. | |
*/ | |
public class MyVideoRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | |
private ArrayList<VideoModel> arrayList = new ArrayList<>(); | |
private Context context; | |
public MyVideoRecyclerViewAdapter(ArrayList<VideoModel> arrayList, Context context) { | |
this.arrayList = arrayList; | |
this.context = context; | |
} | |
@Override | |
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | |
int layout = 0; | |
RecyclerView.ViewHolder viewHolder; | |
layout = R.layout.row_layout_video; | |
View memeView = LayoutInflater | |
.from(parent.getContext()) | |
.inflate(layout, parent, false); | |
viewHolder = new MyViewHolder(memeView); | |
return viewHolder; | |
} | |
private static final String TAG = "MyVideoRecyclerViewAdap"; | |
@Override | |
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | |
Log.d(TAG, "onBindViewHolder: " + arrayList.get(position)); | |
VideoModel videoModel = (VideoModel) arrayList.get(position); | |
Double ratio = videoModel.getVideo_width_to_height_ratio(); | |
int width = (int) context.getResources().getDisplayMetrics().widthPixels; | |
int height = (int) (width / ratio); | |
// changing the height of the wrapper Layout of the video Surface keeping in mind the width of the | |
// device and also the aspect ratio of the video | |
if (height > width) { | |
((MyViewHolder) holder).videoContainerLayout.getLayoutParams().height | |
= width; | |
} else if (height < width) { | |
((MyViewHolder) holder).videoContainerLayout.getLayoutParams().height | |
= height; | |
} | |
} | |
@Override | |
public int getItemCount() { | |
return arrayList.size(); | |
} | |
class MyViewHolder extends RecyclerView.ViewHolder { | |
@InjectView(R.id.video_view) | |
SimpleVideoView video_view; | |
@InjectView(R.id.videoContainerLayout) | |
FrameLayout videoContainerLayout; | |
public MyViewHolder(View itemView) { | |
super(itemView); | |
ButterKnife.inject(this, itemView); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="20dp" | |
android:background="#000" | |
android:orientation="vertical"> | |
<!-- For now put the wrapper Layout height | |
as a number and not wrap_content--> | |
<FrameLayout | |
android:id="@+id/videoContainerLayout" | |
android:layout_width="match_parent" | |
android:layout_height="200dp"> | |
<!-- The Video surfaces don't do so good when they are wrap content | |
and it is also not good idea to dynamically change the height of the | |
Video Surfaces | |
so we put the Video surface in a wrapper Layout in this case a FrameLayout | |
and make the surface match parent for height and width | |
and make the dynamic height changes to the wrapper Layout instead | |
--> | |
<com.klinker.android.simple_videoview.SimpleVideoView | |
android:id="@+id/video_view" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
app:loop="true" | |
app:muted="false" | |
app:showSpinner="true" | |
app:stopSystemAudio="true" /> | |
<!-- This imageView will be used as a cover thumbnail --> | |
<ImageView | |
android:id="@+id/iv_cover" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" /> | |
<!-- android:background="@color/colorPrimary" --> | |
</FrameLayout> | |
</RelativeLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package pune.university.engineering.gifutilstest; | |
import android.content.ContentResolver; | |
import android.content.Context; | |
import android.net.Uri; | |
import android.os.Handler; | |
import android.support.annotation.AnyRes; | |
import android.support.annotation.NonNull; | |
import android.support.v7.app.AppCompatActivity; | |
import android.os.Bundle; | |
import android.support.v7.widget.LinearLayoutManager; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.Log; | |
import android.view.View; | |
import android.widget.AbsListView; | |
import android.widget.ImageView; | |
import com.danikula.videocache.HttpProxyCacheServer; | |
import com.klinker.android.simple_videoview.SimpleVideoView; | |
import com.squareup.picasso.Picasso; | |
import java.util.ArrayList; | |
import butterknife.ButterKnife; | |
import butterknife.InjectView; | |
public class VideoListActivity extends AppCompatActivity { | |
private static final String TAG = "VideoListActivity"; | |
@InjectView(R.id.recyclerView) | |
RecyclerView mRecyclerView; | |
private LinearLayoutManager mLayoutManager; | |
private int mScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; | |
private ArrayList<VideoModel> arrayList = new ArrayList<>(); | |
MyVideoRecyclerViewAdapter videoRecyclerViewAdapter; | |
// This Method returns the URI in string of the drawable passed as argument | |
public static final String getUriToDrawable(@NonNull Context context, | |
@AnyRes int drawableId) { | |
Uri imageUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + | |
"://" + context.getResources().getResourcePackageName(drawableId) | |
+ '/' + context.getResources().getResourceTypeName(drawableId) | |
+ '/' + context.getResources().getResourceEntryName(drawableId)); | |
return imageUri.toString(); | |
} | |
@Override | |
protected void onPause() { | |
super.onPause(); | |
// We need to pause any playback when the application is minimised | |
try { | |
int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition(); | |
SimpleVideoView video_view = (SimpleVideoView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition).findViewById(R.id.video_view); | |
video_view.release(); | |
} catch (NullPointerException e) { | |
// Sometimes you scroll so fast that the views are not attached so it gives a NullPointerException | |
} catch (ArrayIndexOutOfBoundsException e) { | |
} | |
} | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_video_list); | |
ButterKnife.inject(this); | |
mRecyclerView.setHasFixedSize(true); | |
mLayoutManager = new LinearLayoutManager(this); | |
mRecyclerView.setLayoutManager(mLayoutManager); | |
// String array of URL of some sample Videos | |
String[] video_urls = new String[]{"http://clips.vorwaerts-gmbh.de/VfE_html5.mp4", | |
"http://techslides.com/demos/sample-videos/small.webm", | |
"http://mirrors.standaloneinstaller.com/video-sample/TRA3106.flv", | |
"http://mirrors.standaloneinstaller.com/video-sample/lion-sample.3gp", | |
"http://mirrors.standaloneinstaller.com/video-sample/dolbycanyon.mp4" | |
}; | |
// Double array of Width to Height Ratio of the above videos manually pre calculated by me | |
Double[] video_width_to_height_ratio = new Double[]{1.777, | |
1.75, | |
1.451612, | |
1.2222, | |
1.2222 | |
}; | |
// Will be used to change the height of the Row Layout according to the height of the Video | |
// we use ratio of width and height and not only height because different devices have different dimensions | |
// String[] of Uris of the Thumbnails of the videos manually extracted by me | |
final String[] thumbnails_string = new String[]{ | |
getUriToDrawable(this, R.drawable.vfe_html5), | |
getUriToDrawable(this, R.drawable.small), | |
getUriToDrawable(this, R.drawable.tra3106), | |
getUriToDrawable(this, R.drawable.lion_sample), | |
getUriToDrawable(this, R.drawable.dolbycanyon), | |
}; | |
// Now putting that data in an ArrayList | |
for (int i = 0; i < video_urls.length; i++) { | |
// Object from the Video caching library com.danikula:videocache | |
HttpProxyCacheServer httpProxyCacheServer = new HttpProxyCacheServer(this); | |
// Passing the url of videos from the string[] video_urls to com.danikula:videocache | |
String proxy_url = httpProxyCacheServer.getProxyUrl(video_urls[i]); | |
// com.danikula:videocache will cache the video from the Url and save it in phone memory | |
// the proxy_url will be the local path to the video and look something like this | |
// file:///data/data/<Your Package Name>/cache/video-cache/0228e681cad575e5352b203922b5229b.m4v | |
// Now put this proxy_url in your arrayList instead of the video _url | |
arrayList.add(new VideoModel(proxy_url, video_width_to_height_ratio[i], thumbnails_string[i])); | |
// and we are done with the Video caching | |
} | |
videoRecyclerViewAdapter = new MyVideoRecyclerViewAdapter(arrayList, this); | |
mRecyclerView.setAdapter(videoRecyclerViewAdapter); | |
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { | |
// Position of the row that is active | |
int activeAdapter = 0; | |
@Override | |
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { | |
super.onScrolled(recyclerView, dx, dy); | |
// Get the index of the first Completely visible item | |
int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition(); | |
Log.d(TAG, "onScrolled: firstCompletelyVisibleItemPosition : " + firstCompletelyVisibleItemPosition); | |
// Even if we scroll by a few millimeters the video will start playing from the beginning | |
// So we need to check if the new Active row layout position is equal to the current active row layout position | |
if (activeAdapter != firstCompletelyVisibleItemPosition) { | |
try { | |
VideoModel videoModel = (VideoModel) arrayList.get(firstCompletelyVisibleItemPosition); | |
String video_url = videoModel.getVideo_url(); | |
SimpleVideoView video_view = (SimpleVideoView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition).findViewById(R.id.video_view); | |
final ImageView iv_cover = (ImageView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition).findViewById(R.id.iv_cover); | |
// Start playing the video in Active row layout | |
video_view.start(Uri.parse(video_url)); | |
// assign this row layout position as active row Layout | |
activeAdapter = firstCompletelyVisibleItemPosition; | |
Log.d(TAG, "onScrolled: activeAdapter : " + activeAdapter); | |
// Hide the thumbnail ImageView with a delay of 300 millisecond or else there will be black | |
// screen before a video plays | |
new Handler().postDelayed(new Runnable() { | |
@Override | |
public void run() { | |
iv_cover.setVisibility(View.INVISIBLE); | |
} | |
}, 300); | |
} catch (NullPointerException e) { | |
// Sometimes you scroll so fast that the views are not attached so it gives a NullPointerException | |
} catch (ArrayIndexOutOfBoundsException e) { | |
} | |
/* Get the Video Surface directly above the fully visible Row Layout so that you may stop the playback | |
when a new row Layout is fully visible | |
*/ | |
if (firstCompletelyVisibleItemPosition >= 1) { | |
try { | |
SimpleVideoView video_view_above = (SimpleVideoView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition - 1).findViewById(R.id.video_view); | |
video_view_above.release(); | |
VideoModel videoModel = (VideoModel) arrayList.get(firstCompletelyVisibleItemPosition - 1); | |
String thumbnail_string = videoModel.getThumbnail(); | |
// video_view_above.start(Uri.parse(thumbnail_string)); | |
ImageView iv_cover_above = (ImageView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition - 1).findViewById(R.id.iv_cover); | |
// Show the cover Thumbnail ImageView | |
iv_cover_above.setVisibility(View.VISIBLE); | |
Picasso.with(VideoListActivity.this) | |
.load((Uri.parse(thumbnail_string))) | |
.into(iv_cover_above); | |
// video_view_above.setBackground(Uri.parse(thumbnail_string)); | |
} catch (NullPointerException e) { | |
} catch (ArrayIndexOutOfBoundsException e) { | |
} | |
} | |
/* Get the Video Surface directly Below the fully visible Row Layout so that you may stop the playback | |
when a new row Layout is fully visible | |
*/ | |
if (firstCompletelyVisibleItemPosition + 1 < arrayList.size()) { | |
try { | |
SimpleVideoView video_view_below = (SimpleVideoView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition + 1).findViewById(R.id.video_view); | |
video_view_below.release(); | |
VideoModel videoModel = (VideoModel) arrayList.get(firstCompletelyVisibleItemPosition + 1); | |
String thumbnail_string = videoModel.getThumbnail(); | |
// video_view_below.start(Uri.parse(thumbnail_string)); | |
ImageView iv_cover_below = (ImageView) mLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition + 1).findViewById(R.id.iv_cover); | |
iv_cover_below.setVisibility(View.VISIBLE); | |
Picasso.with(VideoListActivity.this) | |
.load((Uri.parse(thumbnail_string))) | |
.into(iv_cover_below); | |
} catch (NullPointerException e) { | |
} catch (ArrayIndexOutOfBoundsException e) { | |
} | |
} | |
} | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package pune.university.engineering.gifutilstest; | |
/** | |
* Created by Altair on 5/28/2018. | |
*/ | |
public class VideoModel { | |
String video_url; | |
Double video_width_to_height_ratio; | |
String thumbnail; | |
public String getVideo_url() { | |
return video_url; | |
} | |
public Double getVideo_width_to_height_ratio() { | |
return video_width_to_height_ratio; | |
} | |
public String getThumbnail() { | |
return thumbnail; | |
} | |
public VideoModel(String video_url, Double video_width_to_height_ratio, String thumbnail) { | |
this.video_url = video_url; | |
this.video_width_to_height_ratio = video_width_to_height_ratio; | |
this.thumbnail = thumbnail; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is this code working?