You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:55:15