在recyclerview中使用textureview播放视频
Posted
技术标签:
【中文标题】在recyclerview中使用textureview播放视频【英文标题】:Playing video using textureview in recyclerview 【发布时间】:2015-09-07 18:59:21 【问题描述】:我正在尝试使用 vine 或 Instagram 应用等视频来实现列表。当列表项显示或完全可见时,他们播放视频的位置,当列表项被隐藏时,视频暂停。我正在使用带有媒体播放器的纹理视图来播放来自 url 的视频,并将其添加为 recyclerview 中的列表项。以下是我的代码。
VideosAdapter 类:
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 类:
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();
当我运行此代码时,其中存在多个问题。
1) 前两个项目/视频缓冲并播放正常。但是当我滚动它时,它不会加载第三个视频,并且第一个视频也会从列表中删除。
2) 在滚动视频/列表项时,开始为已缓冲的项目再次缓冲。
3) 在快速滚动列表中会变得太迟钝,卡住并崩溃。
附件是我在列表滚动和视频播放时得到的 logcat 图像。
谁能指导我完成这个?像 vine app 这样创建列表的正确方法是什么?
【问题讨论】:
对同时播放的视频数量存在设备限制。确保在开始播放新视频之前停止播放上一个视频。 “M”版本计划添加一个 API,它会告诉您限制是什么——developer.android.com/preview/api-overview.html#video。 感谢您的回复。我添加了 3 个网址来播放。但没有开始播放并评论了这一行 mp.start();从 startVideo(surface) 方法开始。当我滚动时,我遇到了 listview 的问题卡住了 5-6 秒。为什么我会收到此问题如果此问题与播放或一次播放多个视频有关,因为现在我正在准备所有视频但尚未开始播放。 【参考方案1】:我能够通过首先从 url 下载视频然后使用自定义播放器播放来实现这一点。如果其他人需要,我会这样做:
1) 获取所有需要播放的url
2) 开始从本地存储中的 url 下载视频(在队列中),并在首选项中保留一个标志(视频是否已下载)
3) 将 url 分配给 Adapter,在其中初始化处理视频播放的视频播放器控制器的对象
4) 设置 addOnScrollListener 以检查当前可见的位置/视频,并检查视频是否已经下载,如果是,则播放它。
以下是完整代码:
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);
视频适配器
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();
视频下载器
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;
视频播放器
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);
【讨论】:
嗨@Nouman Bhatti。您可以将此代码作为单个项目共享吗?这对我完全有帮助。谢谢。 你能在工作模式下分享你的完整源代码吗?或者通过 test4fusioni@gmail.com 给我发邮件。谢谢 对不起,我现在没有完整的源代码,因为它是一个旧项目。但是上面的代码除了xml文件外几乎是完整的 我可以为视频项目设置自定义视图 xml 吗?因为在 My RecyclerView 中有两个单元格,一个用于视频,另一个用于图像.. 是的,你可以做到,但我没有完全理解你的意思。如果 videoview 和 imageview 有单独的单元格,那么问题是什么?【参考方案2】:为什么不在布局文件“view_main”本身中添加自定义视频视图。 检查视频视图的可见性并仅在视图可见时播放。
public static boolean isViewVisible(View subView, View parentView)
Rect scrollBounds = new Rect();
parentView.getHitRect(scrollBounds);
if (subView.getLocalVisibleRect(scrollBounds))
return true;
return false;
检查可见性的代码。当滚动状态为空闲时,在滚动状态更改侦听器中调用此方法。
此外,您还必须使用 AsyncTask 来下载视频,但一次只能下载一个视频,否则您可能会出现内存不足错误。
【讨论】:
不能使用视频视图,因为它不适用于滚动。您还可以详细说明在哪里调用上述方法来获取可见的列表视图项? 视频视图是指您的自定义视图。 您必须覆盖列表的 onScrollListener 并获取顶部可见项目。顺便问一下,你使用 Recycler 视图还是 List 视图? 我正在使用回收站视图 我不能这样做,因为在那种情况下,我只能访问那个可见的视图,但在可见视图的基础上,我还需要决定可见视图的下一个和上一个视图应该停止并暂停 【参考方案3】:您应该通过在后端下载视频来维护本地缓存,并从本地内存一次播放一个视频,以保持列表滚动流畅。
【讨论】:
您更喜欢用什么来实现视频缓存? @sajad-khan以上是关于在recyclerview中使用textureview播放视频的主要内容,如果未能解决你的问题,请参考以下文章
如何在 RecyclerView 中使用 ItemAnimator?
在选项卡式活动中使用 RecyclerView 的 OnScreenRotation
如何在 RecyclerView 中使用 offsetChildrenHorizontal()
如何在我的 recyclerview 中使用 cardview 设置 onclick 侦听器,并在单击 recyclerview 时从 firebase 数据库中检索数据?