Android 列表视频滑动自动播放—滑动过程自动播放(实现思路)

本文基于Exoplayer +  PlayerView 实现列表视频显示一定比例后自动播放

首先引入google media3包

implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'
implementation "androidx.media3:media3-common:1.1.1"

列表自动播放,我们需要监听recyclerView.addOnScrollListener(this),并实现对应方法

onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) 中判断滑动过程中,展示视频itemVideoView比例

1、首先我们需要拿到recyclerView显示出来的itemPosition

int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();

2、当判断第一个(firstItemPosition)和最后一个(lastItemPosition)都大于0时候,开始遍历recyclerView数据,判断显示视频itemVideoView显示比例(因为我项目,视频播放数据是二维数组。RecyclerView1+RecyclerView2结构,所以视频播放时在里层RecyclerView2,如果不是二维,那么就RecyclerView2其实就是视频播放容器,dataItem.getVideoPosition()其实就是告诉我们RecyclerView2中第几条数据播放视频,默认是-1,表示没有视频,需要我们在网络请求后,遍历数据生成对应可以展示视频position)

 public void recyclerViewScrollVideo() {
        if (recyclerView == null || linearLayoutManager == null || communityPostListAdapter == null) {
            return;
        }
        int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
        int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();
        if (firstItemPosition >= 0 && lastItemPosition >= 0) {
            DataItem dataItem = communityPostListAdapter.getData(firstItemPosition);
            CommonViewHolder viewHolder = (CommonViewHolder) recyclerView.findViewHolderForAdapterPosition(firstItemPosition);
            if (viewHolder != null) {
                //当第一个显示数据,不支持播放视频时候,需要直接遍历后面,看是否有支持视频的
                //获取里层 RecyclerView Image
                RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);
                if (dataItem != null && dataItem.getVideoPosition() >= 0 && recyclerImage != null) {
                    //获取里层视频那个itemView position
                    int videoPosition = dataItem.getVideoPosition();
                    CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(videoPosition);
                    //获取当前视频itemBen
                    VideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();
                    //获取视频数据对象
                    VideoDataBean videoDataBean = null;
                    if (videoDataBean != null) {
                        videoDataBean = videoAdapter.getData(videoPosition);
                    }
                    boolean isPlaying = ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, firstItemPosition, videoDataBean);
                    if (!isPlaying) {
                        //firstItemPosition不支持播放,则遍历RecyclerView数据判断下一个是否支持播放
                        int newFirstItemPosition = firstItemPosition + 1;
                        traversal(recyclerView, newFirstItemPosition, lastItemPosition);
                    }
                } else {
                    //有可能是多布局,则第一条数据,找不到视频播放view,那么我们需要遍历数据,从第二条数据开始找
                    int newFirstItemPosition = firstItemPosition + 1;
                    traversal(recyclerView, newFirstItemPosition, lastItemPosition);
                }

            }

        }
    }

3、当第一条不满足,开始遍历

 /**
     * 遍历寻找,屏幕显示可以播放视频的item
     *
     * @param recycler
     * @param newFirstItemPosition
     * @param lastItemPosition
     */
    private void traversal(RecyclerView recycler, int newFirstItemPosition, int lastItemPosition) {
        //标记是否找到视频-1,表示未找到视频
        int playPosition = -1;
        for (int i = newFirstItemPosition; i <= lastItemPosition; i++) {
            DataItem dataItem = communityPostListAdapter.getData(i);
            CommonViewHolder viewHolder = (CommonViewHolder) recycler.findViewHolderForAdapterPosition(i);
            if (viewHolder != null && dataItem != null && dataItem.getVideoPosition() >= 0) {
                RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);
                if (recyclerImage != null) {
                    CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(dataItem.getVideoPosition());
                    //获取当前视频itemBen
                    VideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();
                    VideoDataBean videoDataBean = null;
                    if (videoAdapter != null) {
                        videoDataBean = videoAdapter.getData(dataItem.getVideoPosition());
                    }
                    if (ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, i, communalImageBean)) {
                        playPosition = i;
                        break;
                    }

                }

            }
        }
        if (playPosition == -1) {
            //都没有找到视频
            ExoPlayerManager.getInstance().stopVideo();
        }
    }

4、视频播放器单例(Exoplayer +  PlayerView)


public class ExoPlayerManager {

    private static volatile ExoPlayerManager inStance = null;
    ExoPlayer mExoPlayer;
    public ExoPlayerListener mExoPlayerListener;
    private UserPlayerView mUserPlayerView;
    //视频容器
    private FrameLayout mLayout;
    //loading view
    private ProgressBar mLoading;
    //视频logo
    private ImageView mLogo;
    //点击播放按钮
    private ImageView mPlay;

    //计算RecyclerView显示位置
    private Rect mItemRect;
    //RecyclerView显示高度
    float visibleHeight;
    //RecyclerView内容高度
    float totalHeight;
    //RecyclerView 显示高度和高度比例
    float visibleRatio;
    int mItemPlayPosition;
    //视频播放数据对象
    VideoDataBean mVideoDataBean;
    //视频最先显示比例
    private float minRatio;

    /**
     * 监听
     */
    private AnalyticsListener mAnalyticsListener = new AnalyticsListener() {
        @Override
        public void onPlaybackStateChanged(EventTime eventTime, int state) {
            AnalyticsListener.super.onPlaybackStateChanged(eventTime, state);
            switch (state) {
                case Player.STATE_IDLE:  //未加载到资源,停止播放,包含无网也会执行
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayIdle();
                    } else {
                        stopShowView();
                    }
                    showController();
                    break;
                case Player.STATE_BUFFERING: //缓冲中
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayBuffering(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);
                    } else {
                        loadingShowView();
                    }
                    break;

                case Player.STATE_READY:  //开始播放
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlay(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);
                    } else {
                        startShowView();
                    }

                    break;

                case Player.STATE_ENDED:  //播放结束,当循环播放时候,改方法不会被触发  onPositionDiscontinuity  DISCONTINUITY_REASON_AUTO_TRANSITION
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayerCompletion();
                    }
                    break;
            }
        }

        @Override
        public void onPositionDiscontinuity(EventTime eventTime, Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
            AnalyticsListener.super.onPositionDiscontinuity(eventTime, oldPosition, newPosition, reason);
            if (mExoPlayerListener != null) {
                mExoPlayerListener.onVideoPlayerCompletion();
            } else {
                //循环播放了,显示时间
                if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
                    showController();
                }
            }

        }

        @Override
        public void onPlayerError(EventTime eventTime, PlaybackException error) {
            AnalyticsListener.super.onPlayerError(eventTime, error);
            if (mExoPlayerListener != null) {
                mExoPlayerListener.onVideoPlayerError(eventTime != null ? eventTime.currentPlaybackPositionMs : -1l);
            } else {
                Toast.makeText(CommonParam.getInstance().getApplication(), "网络异常", Toast.LENGTH_LONG).show();
                if (eventTime != null) {
                    long currentPlaybackPositionMs = eventTime.currentPlaybackPositionMs;
                    if (mVideoDataBean != null && currentPlaybackPositionMs > 0) {
                        mVideoDataBean.setSeekToPositionMs(currentPlaybackPositionMs);
                    }
                }
                //网络异常会执行--onPlayerError---onPlaybackStateChanged(1)----onIsPlayingChanged()
                stopShowView();
            }
        }
    };

    public static ExoPlayerManager getInstance() {
        if (inStance == null) {
            synchronized (ExoPlayerManager.class) {
                if (inStance == null) {
                    inStance = new ExoPlayerManager();
                }
            }
        }
        return inStance;
    }

    private ExoPlayerManager() {
        init();
    }


    private void init() {
        //初始化exoPlayer
        if (mExoPlayer == null) {
            mExoPlayer = new ExoPlayer.Builder(CommonParam.getInstance().getApplication()).build();
            mExoPlayer.addAnalyticsListener(mAnalyticsListener);
        }
        // 设置重复模式
        // Player.REPEAT_MODE_ALL 无限重复
        // Player.REPEAT_MODE_ONE 重复一次
        // Player.REPEAT_MODE_OFF 不重复
        mExoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
        setVolume(CommonParam.getInstance().isVideoVolumeChange());

        if (mItemRect == null) {
            mItemRect = new Rect();
        }
        if (mUserPlayerView == null) {
            mUserPlayerView = new UserPlayerView(CommonParam.getInstance().getApplication());
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            mUserPlayerView.setLayoutParams(layoutParams);
        }
        //设置视频填充模式  RESIZE_MODE_FILL 拉伸视频达到没有黑边效果,RESIZE_MODE_ZOOM:这个是居中填充效果
        mUserPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH);
        minRatio = 9f / 16f;
    }


    /**
     * 获取视频View
     *
     * @return
     */
    public UserPlayerView getUserPlayerView() {
        return mUserPlayerView;
    }

    /**
     * 添加监听
     */
    public void addAnalyticsListener() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
            mExoPlayer.addAnalyticsListener(mAnalyticsListener);
        }
    }

    /**
     * 移除监听
     */
    public void removeAnalyticsListener() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
        }
    }

    /**
     * 视频显示尺寸修改
     *
     * @param params
     * @param playerView
     * @param maxWidth   屏幕显示最大宽
     * @param width      视频真实宽
     * @param height     视频真实高
     */
    public void disposeVideoSize(ViewGroup.LayoutParams params, View playerView, int maxWidth, float width, float height) {
        if (params != null && playerView != null) {
            if (width > 0 && height > 0) {
                if (height >= width) {
                    int showWidth = (int) (maxWidth > 0 ? maxWidth : width);
                    params.width = showWidth;
                    params.height = showWidth;
                } else {
                    params.width = maxWidth;
                    float ratio = height / width;
                    params.height = (int) ((ratio >= minRatio) ? ratio * maxWidth : minRatio * maxWidth);
                }

            } else if (maxWidth > 0) {
                params.width = maxWidth;
                params.height = maxWidth;
            } else {
                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
            }
            playerView.setLayoutParams(params);
        }
    }


  

    /**
     * 获取当前列表布局控件,一会计算时候需要用
     *
     * @param recyclerImage
     * @param itemViewHolder
     * @param communalImageBean
     */
    public boolean recyclerViewScrollVideo(RecyclerView recyclerImage, CommonViewHolder itemViewHolder, int itemPlayPosition, CommunalImageBean communalImageBean) {
        if (recyclerImage != null && itemViewHolder != null && communalImageBean != null) {
            FrameLayout frameLayout = itemViewHolder.getView(R.id.video);
            ImageView logo = itemViewHolder.getView(R.id.logo);
            ImageView play = itemViewHolder.getView(R.id.play);
            ProgressBar loading = itemViewHolder.getView(R.id.loading);
            ImageView volume = itemViewHolder.getView(R.id.volume);
            setVolumeViewChange(volume);
            setVolume(CommonParam.getInstance().isVideoVolumeChange());
            return calculateVideoPlay(recyclerImage, frameLayout, play, logo, loading, itemPlayPosition, communalImageBean);
        }
        return false;
    }

    /**
     * 计算是否滑动自动播放视频
     *
     * @param view
     * @param frameLayout
     * @param play
     * @param logo
     * @param loading
     * @param videoDataBean
     */
    public boolean calculateVideoPlay(View view, FrameLayout frameLayout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition,VideoDataBean videoDataBean ) {
        view.getLocalVisibleRect(mItemRect);
        visibleHeight = mItemRect.bottom - mItemRect.top;
        totalHeight = view.getHeight();
        visibleRatio = visibleHeight / totalHeight;
        if (mItemRect.top >= 0 && visibleRatio > BaseConstantValue.VIDEO_START_VISIBLE_RATIO) {
            playVideo(frameLayout, play, logo, loading, itemPlayPosition, videoDataBean);
            return true;
        } else {
            //不满足播放
            if (play != null) {
                play.setVisibility(View.VISIBLE);
            }
            if (loading != null) {
                loading.setVisibility(View.GONE);
            }
            if (logo != null) {
                logo.setVisibility(View.VISIBLE);
            }
            if (frameLayout != null) frameLayout.removeAllViews();

        }
        return false;
    }

    /**
     * 满足播放视频,需要再判断,是不是已经还在播放当前视频
     *
     * @param layout
     * @param play
     * @param logo
     * @param loading
     * @param itemPlayPosition
     * @param videoDataBean
     */
    public void playVideo(FrameLayout layout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition, VideoDataBean videoDataBean) {
        if (mItemPlayPosition != itemPlayPosition) {
            //不满足当前播放那么需要停止播放,做部分资源保存
            if (videoDataBean != null && mExoPlayer.isPlaying()) {
                //视频已经播放位置
                videoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());
            }
            //显示暂停不播布局
            stopShowView();
            //暂停播放
            stopVideo();
        }
        //重新复制相关控件
        mLayout = layout;
        mPlay = play;
        mLogo = logo;
        mLoading = loading;

        //获取当前播放状态,如果未播放,则直接设置资源播放,如果已经播放,则不做处理
        if (!mExoPlayer.isPlaying()) {
            //设置播放布局
            loadingShowView();
            //移除播放View
            if (mUserPlayerView != null) {
                ViewParent parent = mUserPlayerView.getParent();
                if (parent != null && parent instanceof FrameLayout) {
                    ((FrameLayout) parent).removeAllViews();
                }
            }

            //将PlayerView 添加 FrameLayout上
            if (layout != null) {
                layout.removeAllViews();
                layout.addView(mUserPlayerView);
            }
            if (videoDataBean != null) {
                String videoUrl = videoDataBean.getVideoUrl();
                long seekToPositionMs = videoDataBean.getSeekToPositionMs();
                long durationMs = videoDataBean.getDurationMs();
                mUserPlayerView.setVideoPosition(seekToPositionMs, durationMs);
                MediaItem mediaItem = MediaItem.fromUri(videoUrl != null ? videoUrl : "");
                mExoPlayer.setMediaItem(mediaItem, seekToPositionMs);
            }

            mUserPlayerView.setPlayer(mExoPlayer);

            mExoPlayer.prepare();
//            mExoPlayer.play();
            mExoPlayer.setPlayWhenReady(true);
        }

        mItemPlayPosition = itemPlayPosition;
        mVideoDataBean = videoDataBean;
    }


    public ExoPlayer exoPlayer() {
        return mExoPlayer;
    }

    /**
     * 是否正在播放
     *
     * @return
     */
    public boolean isPlaying() {
        return mExoPlayer != null ? mExoPlayer.isPlaying() : false;
    }

    /**
     * 获取当前播放进度
     *
     * @return
     */
    public long getCurrentPositionMs() {
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            return mExoPlayer.getCurrentPosition();
        }
        return -1;
    }

    /**
     * 获取总时长
     *
     * @return
     */
    public long getDurationMs() {
        if (mExoPlayer != null) {
            return mExoPlayer.getDuration();
        }
        return 0l;
    }

    /**
     * 设置进度
     *
     * @param seekToCurrentPositionMs
     */
    public void seekTo(long seekToCurrentPositionMs) {
        if (mExoPlayer != null) {
            mExoPlayer.seekTo(seekToCurrentPositionMs);
            if (!isPlaying()) {
                mExoPlayer.play();
            }
        }
    }

    /**
     * 设置是否有视频控制器
     *
     * @param useController
     */
    private void setUseController(boolean useController) {
        mUserPlayerView.setUseController(useController);

    }

    /**
     * 绑定全屏播放
     *
     * @param videoUrl
     * @param currentPositionMs
     * @return
     */
    private UserPlayerView bindVideoFullScreen(String videoUrl, long currentPositionMs) {
        mItemPlayPosition = -1;
        mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);
        mUserPlayerView.setPlayer(mExoPlayer);
        mExoPlayer.prepare();
        mVideoDataBean = null;
        return mUserPlayerView;
    }


    /**
     * 开始播放
     */
    public void startVideo() {
        if (mExoPlayer != null && !mExoPlayer.isPlaying()) {
            mExoPlayer.play();
        }
    }

    /**
     * 开始播放
     */
    public void startVideo(long currentPositionMs) {
        if (mExoPlayer != null && !mExoPlayer.isPlaying()) {
            mExoPlayer.seekTo(currentPositionMs);
            mExoPlayer.play();
        }
    }

    /**
     * 开始播放
     */
    public void startVideo(String videoUrl, long currentPositionMs) {
        if (mExoPlayer != null) {
            if (!mExoPlayer.isPlaying()) {
                mExoPlayer.stop();
            }
            mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);
            mUserPlayerView.setPlayer(mExoPlayer);
            mExoPlayer.prepare();
            mExoPlayer.play();
        }

    }

    /**
     * 暂停播放
     */
    public void pauseVideo() {
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            mExoPlayer.pause();
        }
    }

    /**
     * 停止播放
     */
    public void stopVideo() {
        mItemPlayPosition = -1;
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            if (mVideoDataBean != null) {
                mVideoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());
            }
           
        }
 mExoPlayer.stop();
        releaseView();
    }

    /**
     * 页面关闭,释放资源
     */
    public void releaseVideo() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
            mExoPlayer.release();
            mExoPlayer = null;
        }
        releaseView();
    }

    private void releaseView() {
        if (mUserPlayerView != null) {
            ViewParent parent = mUserPlayerView.getParent();
            if (parent != null && parent instanceof FrameLayout) {
                ((FrameLayout) parent).removeAllViews();
            }
        }
        //将PlayerView 添加 FrameLayout上
        if (mLayout != null) {
            mLayout.removeAllViews();
            mLayout = null;
        }
        if (mPlay != null) {
            mPlay = null;
        }
        if (mLogo != null) {
            mLogo = null;
        }
        if (mLoading != null) {
            mLoading = null;
        }
        mVideoDataBean = null;
    }

    /**
     * 设置播放器是否开启声音
     *
     * @param volumeChange
     */
    public void setVolume(boolean volumeChange) {
        if (volumeChange) {
            //设置声音
            if (mExoPlayer.getVolume() == 0f) {
                AudioManager audioManager = (AudioManager) CommonParam.getInstance().getApplication().getSystemService(Context.AUDIO_SERVICE);
                float streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
                float streamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                float volume = (streamMaxVolume != 0 && streamVolume != 0) ? streamVolume / streamMaxVolume : 0.3f;
                mExoPlayer.setVolume(volume);
            }
        } else {
            //设置声音为0
            mExoPlayer.setVolume(0f);
        }
        CommonParam.getInstance().setVideoVolumeChange(volumeChange);
    }

    /**
     * 设置 音量开关状态
     *
     * @param volumeView
     */
    public void setVolumeViewChange(View volumeView) {
        if (volumeView != null) {
            volumeView.setSelected(CommonParam.getInstance().isVideoVolumeChange());
        }
    }

    /**
     * 视频正在播放,设置相关布局show or hide
     */
    public void startShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.GONE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.GONE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.GONE);
        }
    }

    /**
     * 视频正在加载中,设置相关布局show or hide
     */
    public void loadingShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.VISIBLE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.VISIBLE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.GONE);
        }
    }

    /**
     * 视频停止播放,设置相关布局show or hide
     */
    public void stopShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.GONE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.VISIBLE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.VISIBLE);
        }
    }

    /**
     * 显示视频控制器
     */
    public void showController() {
        if (mUserPlayerView != null) {
            UserPlayerControlView controller = mUserPlayerView.getController();
            if (controller != null) {
                controller.show();
            }
        }
    }

    /**
     * 设置监听
     *
     * @param exoPlayerListener
     */
    public void setExoPlayerListener(ExoPlayerListener exoPlayerListener) {
        mExoPlayerListener = exoPlayerListener;
    }
}

5、建议我们在开发视频时自定义PlayerView,只需要把源码中,这三个文件拷贝一份,然后自己根据具体需求,实现对应功能。

①、PlayerView

②、PlayerControlView

③、PlayerControlViewLayoutManager

6、如果项目是appBarLayout+RecyclerView的话。我们需要对appBarLayout滑动事件进行监听,从而实现appBarLayout滑动,也能播放视频

  mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.BaseOnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                //appBarLayout 滑动监听
                int newOffset = Math.abs(verticalOffset);
                if (mVerticalOffset != newOffset && adapter != null) {
                    Fragment fragment = adapter .getFragment(viewPagerShowType);
                    if (fragment != null && fragment.isOnResume()) {
                        fragment.startVideo();
                    }
                    mVerticalOffset = newOffset;
                }

            }
        });

相关推荐

  1. Android 列表视频滑动自动播放(实现思路)

    2024-07-11 21:08:01       27 阅读
  2. 使用VLC实现自动播放视频

    2024-07-11 21:08:01       37 阅读
  3. html 中video实现切换视频自动播放

    2024-07-11 21:08:01       48 阅读
  4. dplayer播放hls格式视频自动开始播放

    2024-07-11 21:08:01       83 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 21:08:01       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 21:08:01       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 21:08:01       58 阅读
  4. Python语言-面向对象

    2024-07-11 21:08:01       69 阅读

热门阅读

  1. 【C#】遍历文件夹及其子文件夹指定后缀文件

    2024-07-11 21:08:01       19 阅读
  2. C语言从头学33——内存管理(一)

    2024-07-11 21:08:01       23 阅读
  3. Qt 的Q_PROPERTY关键字

    2024-07-11 21:08:01       20 阅读
  4. C++ 入门08:运算符重载

    2024-07-11 21:08:01       24 阅读
  5. [AI 大模型] 百度 文心一言

    2024-07-11 21:08:01       20 阅读
  6. 架构面试-场景题-单点登录(SSO)怎么实现的

    2024-07-11 21:08:01       24 阅读
  7. 深入理解Spring Boot中的事件驱动架构

    2024-07-11 21:08:01       21 阅读
  8. DDD架构面试问题

    2024-07-11 21:08:01       21 阅读
  9. 解析 pdfminer pdfparser.py

    2024-07-11 21:08:01       23 阅读
  10. 解决vue3子组件onMounted中获取不到props的值

    2024-07-11 21:08:01       17 阅读