Android实现滑动后无延迟播放短视频(仿Ins/YouTube Shorts)
解决Android短视频滑动切换延迟问题
问题描述
我用Java开发一款Android短视频应用,功能正常,但滑动切换新视频时存在1-2秒的播放延迟,想要实现Instagram Reels或YouTube Shorts那样的即时无停顿播放效果,已经尝试过VideoView和ExoPlayer,但没达到预期。以下是我的代码:
MainActivity代码
public class MainActivity extends AppCompatActivity { List<VideoItem> videoItems = new ArrayList<>(); public static ExoPlayer player ; private String my_id="123"; private String videoURL,userid,username,videoDescription,postid,like_count,dislike_count; private RequestQueue mRequestQueue; ViewPager2 videosViewPager; MediaItem item; @Override protected void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); videosViewPager = findViewById(R.id.viewPagerVideos); mRequestQueue = Volley.newRequestQueue(getApplicationContext()); sendshorts(); player = new ExoPlayer.Builder(getApplicationContext()).build(); player.setRepeatMode(player.REPEAT_MODE_ONE); videosViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { super.onPageScrolled(position, positionOffset, positionOffsetPixels); } @Override public void onPageSelected(int position) { super.onPageSelected(position); Log.d("satyam","current position"+position); player.seekTo(position,1); player.play(); } @Override public void onPageScrollStateChanged(int state) { super.onPageScrollStateChanged(state); } }); } @Override protected void onDestroy() { player.release(); super.onDestroy(); } public void sendshorts() { String url = "myurl"; Map<String, String> params = new HashMap<>(); params.put("my_id", my_id); params.put("url"," " ); final JSONObject jsonObj = new JSONObject(params); JsonObjectRequest jsonObject = new JsonObjectRequest(Request.Method.POST, url, jsonObj, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { JSONArray obj = response.getJSONArray("videosend"); for (int i = 0; i < obj.length(); i++) { JSONObject jsonObject = obj.getJSONObject(i); videoURL = jsonObject.getString("videoURL"); userid = jsonObject.getString("user"); username = jsonObject.getString("name"); videoDescription = jsonObject.getString("videoDescription"); postid = jsonObject.getString("postid"); like_count = jsonObject.getString("like_count"); dislike_count = jsonObject.getString("dislike_count"); VideoItem v = new VideoItem(videoURL,username,videoDescription,userid,postid,like_count,dislike_count); videoItems.add(v); item=MediaItem.fromUri(videoURL); player.addMediaItem(item); player.prepare(); } videosViewPager.setAdapter(new VideosAdapter(videoItems)); }catch (Exception ignored){ } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); mRequestQueue.add(jsonObject); } @Override protected void onPause() { super.onPause(); } }
VideosAdapter代码
public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.VideoViewHolder>{ private static View iv; private List<VideoItem> mVideoItems; public VideosAdapter(List<VideoItem> videoItems) { mVideoItems = videoItems; } @Override public void onViewDetachedFromWindow(@NonNull VideoViewHolder holder) { super.onViewDetachedFromWindow(holder); } @Override public void onViewAttachedToWindow(@NonNull VideoViewHolder holder) { super.onViewAttachedToWindow(holder); MainActivity.player.setVideoTextureView(holder.styledPlayerView); MainActivity.player.play(); } @NonNull @Override public VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new VideoViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_videos_container,parent,false)); } @Override public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) { holder.setVideoData(mVideoItems.get(position)); } @Override public int getItemCount() { return mVideoItems.size(); } class VideoViewHolder extends RecyclerView.ViewHolder{ TextView txtTitle,txtDesc; ProgressBar mProgressBar; TextureView styledPlayerView; public VideoViewHolder(@NonNull View itemView) { super(itemView); styledPlayerView = itemView.findViewById(R.id.videoView); txtTitle = itemView.findViewById(R.id.txtTitle); txtDesc = itemView.findViewById(R.id.txtDesc); } void setVideoData(VideoItem videoItem){ txtTitle.setText(videoItem.username); txtDesc.setText(videoItem.videoDescription); } } }
核心优化方案
1. 重构播放器与ViewHolder绑定逻辑
当前单一ExoPlayer实例切换绑定不同TextureView会导致重建开销,改为每个ViewHolder持有独立播放器,提前预加载:
class VideoViewHolder extends RecyclerView.ViewHolder implements Player.Listener { TextView txtTitle,txtDesc; ProgressBar mProgressBar; TextureView styledPlayerView; ExoPlayer player; public VideoViewHolder(@NonNull View itemView) { super(itemView); styledPlayerView = itemView.findViewById(R.id.videoView); txtTitle = itemView.findViewById(R.id.txtTitle); txtDesc = itemView.findViewById(R.id.txtDesc); mProgressBar = itemView.findViewById(R.id.progress_bar); // 初始化当前ViewHolder专属播放器 player = new ExoPlayer.Builder(itemView.getContext()).build(); player.setRepeatMode(ExoPlayer.REPEAT_MODE_ONE); player.addListener(this); // 绑定TextureView styledPlayerView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { player.setVideoSurface(new Surface(surface)); } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {} @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { player.setVideoSurface(null); return true; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} }); } void setVideoData(VideoItem videoItem){ txtTitle.setText(videoItem.username); txtDesc.setText(videoItem.videoDescription); // 加载媒体资源 MediaItem mediaItem = MediaItem.fromUri(videoItem.videoURL); player.setMediaItem(mediaItem); player.prepare(); player.seekTo(0); } @Override public void onPlaybackStateChanged(int playbackState) { mProgressBar.setVisibility(playbackState == Player.STATE_READY ? View.GONE : View.VISIBLE); } void releasePlayer() { player.pause(); player.release(); } }
同时修改Adapter生命周期方法:
@Override public void onViewAttachedToWindow(@NonNull VideoViewHolder holder) { super.onViewAttachedToWindow(holder); holder.player.play(); } @Override public void onViewDetachedFromWindow(@NonNull VideoViewHolder holder) { super.onViewDetachedFromWindow(holder); holder.player.pause(); } @Override public void onViewRecycled(@NonNull VideoViewHolder holder) { super.onViewRecycled(holder); holder.releasePlayer(); }
2. 开启ExoPlayer本地缓存
避免重复下载视频,提升切换速度:
// 在ViewHolder初始化播放器时添加缓存配置 File cacheDir = new File(itemView.getContext().getCacheDir(), "video_cache"); long maxCacheSize = 100 * 1024 * 1024; // 100MB缓存空间 Cache cache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(maxCacheSize)); DataSource.Factory dataSourceFactory = new CacheDataSource.Factory() .setCache(cache) .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory()); player = new ExoPlayer.Builder(itemView.getContext()) .setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) .build();
3. 优化ViewPager2预加载
让ViewPager2提前预加载相邻页面,切换时视频已就绪:
// MainActivity onCreate中添加 videosViewPager.setOffscreenPageLimit(1); // 预加载前后1个页面 videosViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
4. 调整媒体资源添加逻辑(如果保留单一播放器)
不要在循环中重复调用player.prepare(),批量添加后一次性准备:
// 修改MainActivity的sendshorts方法 List<MediaItem> mediaItems = new ArrayList<>(); for (int i = 0; i < obj.length(); i++) { // ...解析数据 VideoItem v = new VideoItem(videoURL,username,videoDescription,userid,postid,like_count,dislike_count); videoItems.add(v); mediaItems.add(MediaItem.fromUri(videoURL)); } player.addMediaItems(mediaItems); player.prepare(); videosViewPager.setAdapter(new VideosAdapter(videoItems));
5. 移除冗余的页面切换播放逻辑
删除MainActivity中onPageSelected里的player.seekTo和player.play(),让Adapter自行控制播放时机。
内容的提问来源于stack exchange,提问作者Anurag Mishra
相关产品推荐
相关产品推荐

