使用Exoplayer播放器的流媒体视频列表

9

我想要实现一个流媒体视频列表应用程序。我用 RecyclerView 来显示我的列表项。列表项类型包括4种类型:文章、状态、照片和视频。我们只需要关注视频类型。这是我 RecyclerView 的适配器代码:

public class FollowedPostAdapter extends RecyclerView.Adapter implements OnFollowTagCallback, OnLikeCallback {
    private Context context;
    private List<PostItem> newsFeedList;
    public RecyclerView recyclerView;
    public LinearLayoutManager linearLayoutManager;
    private HashMap<Integer, DemoPlayer> playerList;

    private int visibleThreshold = 5;
    // private int previousTotal = 0;
    private int visibleItemCount, firstVisibleItem, totalItemCount;
    private boolean loading;
    private OnRecyclerViewLoadMoreListener loadMoreListener;


    private final String text_comment;
    private final String mReadMoreHtml;
    private long userId;


    public FollowedPostAdapter(Context context, RecyclerView recyclerView, List<PostItem> newsFeedList) {
        this.context = context;
        playerList = new HashMap<>();
        this.newsFeedList = newsFeedList;
        this.recyclerView = recyclerView;
        this.linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder;
        if (viewType == Constants.VIEWTYPE_ARTICLE) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_article_item, parent, false);
            viewHolder = new ArticleViewHolder(view);
        } else if (viewType == Constants.VIEWTYPE_STATUS) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_status_item, parent, false);
            viewHolder = new StatusViewHolder(view);
        } else if (viewType == Constants.VIEWTYPE_PHOTO) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_photo_item, parent, false);
            viewHolder = new PhotoViewHolder(view);
        } else if (viewType == Constants.VIEWTYPE_VIDEO) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_video_item, parent, false);
            viewHolder = new VideoViewHolder(view);
        } else {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.progressbar_item, parent, false);
            viewHolder = new ProgressBarViewHolder(view);
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ArticleViewHolder) {
           // code

        } else if (holder instanceof StatusViewHolder) {
        // code

        } else if (holder instanceof PhotoViewHolder) {
         // code
        } else if (holder instanceof VideoViewHolder) {
            PostItem item = newsFeedList.get(position);
            VideoViewHolder mHolder = (VideoViewHolder) holder;
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mHolder.videoView.getLayoutParams();
            lp.height = (int) (ScreenHelper.getScreenWidth((Activity) context) * Constants.POST_IMAGE_RATIO);
            mHolder.videoView.setLayoutParams(lp);
            mHolder.setup(item);
            Picasso.with(context).load(item.imageCover).error(R.drawable.ic_user_avatar).placeholder(R.drawable.ic_user_avatar).into(mHolder.iv_avatar);
            if (item.tags != null && item.tags.size() > 0) {
                // get first tag as main tag
                generateTagViews(mHolder.tag_flow_layout, item.tags.subList(0, 1), position);
                mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
                mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
                //   mHolder.indicator.setVisibility(View.VISIBLE);
            } else {
                mHolder.tag_flow_layout.setVisibility(View.GONE);
                // mHolder.indicator.setVisibility(View.GONE);
            }
            if (item.time_created != null) {
                mHolder.tv_time.setText(item.time_created);
                //mHolder.indicator.setVisibility(View.VISIBLE);
                mHolder.tv_time.setVisibility(View.VISIBLE);
            } else {
                //mHolder.indicator.setVisibility(View.GONE);
                mHolder.tv_time.setVisibility(View.GONE);
            }
            if (item.description_short.isEmpty())
                mHolder.tv_description.setVisibility(View.GONE);
            else {
                mHolder.tv_description.setText(item.description_short);
                mHolder.tv_description.setVisibility(View.VISIBLE);
            }

            mHolder.btn_comment.setText(String.valueOf(item.count_comment));
            mHolder.btn_like.setText(String.valueOf(item.count_like));
            mHolder.btn_unlike.setText(String.valueOf(item.count_unlike));
            mHolder.btn_share.setText(String.valueOf(item.count_share));
            if (item.tags.size() != 0) {
                int tagId = item.tags.get(0).tag_id;
                setFollowButtonActive(mHolder.btn_follow, TagHelper.isTagFollowed(tagId));
            } else
                setFollowButtonActive(mHolder.btn_follow, false);

        }

    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        super.onViewRecycled(holder);
        if (holder instanceof VideoViewHolder) {
            DemoPlayer player = playerList.get(holder.getAdapterPosition());
            if (player != null) {
                player.release();
                playerList.remove(holder.getAdapterPosition());
            }
        }
    }


    public void pauseAllPlayers() {
        for (int i = 0; i <= newsFeedList.size(); i++) {
            DemoPlayer player = playerList.get(i);
            if (player != null) {
                if (player.getPlayerControl().isPlaying())
                    player.getPlayerControl().pause();
                RecyclerView.ViewHolder holder = recyclerView.findViewHolderForLayoutPosition(i);
                if (holder != null && holder instanceof VideoViewHolder) {
                    ((VideoViewHolder) holder).btn_play.setVisibility(View.VISIBLE);
                }

            }
        }
    }





    public void refreshData() {
        notifyDataSetChanged();
    }

    public class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        //
    }

    public class StatusViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        // code
    }

    public class PhotoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
      // code
    }

    public class VideoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, SurfaceHolder.Callback, AudioCapabilitiesReceiver.Listener, DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener {
        @Bind(R.id.iv_avatar)
        ImageView iv_avatar;
        @Bind(R.id.tag_flow_layout)
        FlowLayout tag_flow_layout;
        @Bind(R.id.tv_time)
        TextView tv_time;
        @Bind(R.id.btn_follow)
        FancyButton btn_follow;
        @Bind(R.id.btn_comment)
        FancyButton btn_comment;
        @Bind(R.id.btn_like)
        FancyButton btn_like;
        @Bind(R.id.btn_unlike)
        FancyButton btn_unlike;
        @Bind(R.id.btn_share)
        FancyButton btn_share;
        @Bind(R.id.root)
        FrameLayout videoView;
        @Bind(R.id.btn_play)
        ImageView btn_play;
        @Bind(R.id.tv_description)
        TextView tv_description;

        // player's variable
        private EventLogger eventLogger;

        //  private VideoControllerView mediaController;
        private View shutterView;
        private AspectRatioFrameLayout videoFrame;
        private SurfaceView surfaceView;
        private SubtitleLayout subtitleLayout;

        private DemoPlayer player;
        private boolean playerNeedsPrepare;

        private long playerPosition = 0;

        private Uri contentUri;
        private int contentType;
        private String contentId;

        public static final int TYPE_DASH = 0;
        public static final int TYPE_SS = 1;
        public static final int TYPE_HLS = 2;
        public static final int TYPE_OTHER = 3;

        private static final String EXT_DASH = ".mpd";
        private static final String EXT_SS = ".ism";
        private static final String EXT_HLS = ".m3u8";

        private AudioCapabilitiesReceiver audioCapabilitiesReceiver;

        public VideoViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
            iv_avatar.setOnClickListener(this);
            btn_follow.setOnClickListener(this);
            btn_comment.setOnClickListener(this);
            btn_like.setOnClickListener(this);
            btn_unlike.setOnClickListener(this);
            btn_share.setOnClickListener(this);

            // player's setup
            View root = view.findViewById(R.id.root);
            root.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    doPlayResume();
                }
            });
//            root.setOnTouchListener(new View.OnTouchListener() {
//                @Override
//                public boolean onTouch(View view, MotionEvent motionEvent) {
//                    if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
//                        toggleControlsVisibility();
//                    } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
//                        view.performClick();
//                    }
//                    return true;
//                }
//            });
//            root.setOnKeyListener(new View.OnKeyListener() {
//                @Override
//                public boolean onKey(View v, int keyCode, KeyEvent event) {
//                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
//                            || keyCode == KeyEvent.KEYCODE_MENU) {
//                        return false;
//                    }
//                    return mediaController.dispatchKeyEvent(event);
//                }
//            });
            shutterView = view.findViewById(R.id.shutter);
            videoFrame = (AspectRatioFrameLayout) view.findViewById(R.id.video_frame);
            surfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
            surfaceView.getHolder().addCallback(this);
            subtitleLayout = (SubtitleLayout) view.findViewById(R.id.subtitles);
//            mediaController = new VideoControllerView(context);
//            mediaController.setAnchorView((ViewGroup) root);
            audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(context, this);
            audioCapabilitiesReceiver.register();
        }

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.iv_avatar:
                    viewTagDetail(getAdapterPosition());
                    break;
                case R.id.btn_follow:
                    followTag((FancyButton) v, getAdapterPosition());
                    break;
                case R.id.btn_comment:
                    commentPost(getAdapterPosition());
                    break;
                case R.id.btn_like:
                    likePost(getAdapterPosition(), btn_like, btn_unlike);
                    break;
                case R.id.btn_unlike:
                    unlikePost(getAdapterPosition(), btn_like, btn_unlike);
                    break;
                case R.id.btn_share:
                    sharePost(getAdapterPosition());
                    break;
                case R.id.btn_play:
                    doPlayResume();
                    break;
            }
        }

        public void setup(PostItem item) {
            releasePlayer();
            player = playerList.get(getAdapterPosition());
            contentUri = Uri.parse(item.link);
            contentType = TYPE_OTHER;
            contentId = String.valueOf(item.post_id);
            configureSubtitleView();
            if (player == null) {
                preparePlayer(false);
            } else {
                player.setBackgrounded(false);
            }
        }

        //        public void saveCurrentPosition() {
//            if (player != null)
//                videoItemList.get(getAdapterPosition()).position = player.getCurrentPosition();
//        }
        public void doPlayResume() {
            if (player == null) {
                return;
            }

            if (player.getPlayerControl().isPlaying()) {
                player.getPlayerControl().pause();
            } else {
                player.getPlayerControl().start();
            }
            showControls();
        }

        @Override
        public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
            if (player == null) {
                return;
            }
            boolean backgrounded = player.getBackgrounded();
            boolean playWhenReady = player.getPlayWhenReady();
            releasePlayer();
            preparePlayer(playWhenReady);
            player.setBackgrounded(backgrounded);
        }

        private DemoPlayer.RendererBuilder getRendererBuilder() {
            String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
            switch (contentType) {
                case TYPE_SS:
                    return new SmoothStreamingRendererBuilder(context, userAgent, contentUri.toString(),
                            new SmoothStreamingTestMediaDrmCallback());
                case TYPE_DASH:
                    return new DashRendererBuilder(context, userAgent, contentUri.toString(),
                            new WidevineTestMediaDrmCallback(contentId));
                case TYPE_HLS:
                    return new HlsRendererBuilder(context, userAgent, contentUri.toString());
                case TYPE_OTHER:
                    return new ExtractorRendererBuilder(context, userAgent, contentUri);
                default:
                    throw new IllegalStateException("Unsupported type: " + contentType);
            }
        }

        private void preparePlayer(boolean playWhenReady) {
            if (player == null) {
                player = new DemoPlayer(getRendererBuilder());
                playerList.put(getAdapterPosition(), player);
                player.addListener(this);
                player.setCaptionListener(this);
                player.setMetadataListener(this);
                player.seekTo(playerPosition);
                playerNeedsPrepare = true;
//                mediaController.setMediaPlayer(player.getPlayerControl());
//                mediaController.setEnabled(true);
                eventLogger = new EventLogger();
                eventLogger.startSession();
                player.addListener(eventLogger);
                player.setInfoListener(eventLogger);
                player.setInternalErrorListener(eventLogger);
            }
            if (playerNeedsPrepare) {
                player.prepare();
                playerNeedsPrepare = false;
            }
            player.setSurface(surfaceView.getHolder().getSurface());
            player.setPlayWhenReady(playWhenReady);
        }

        private void releasePlayer() {
            if (player != null) {
                player.release();
                player = null;
                eventLogger.endSession();
                eventLogger = null;
                btn_play.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onStateChanged(boolean playWhenReady, int playbackState) {
            if (playbackState == ExoPlayer.STATE_ENDED) {
                showControls();
            }

        }

        @Override
        public void onError(Exception e) {
            if (e instanceof UnsupportedDrmException) {
                // Special case DRM failures.
                UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
                int stringId = Util.SDK_INT < 18 ? R.string.drm_error_not_supported
                        : unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
                        ? R.string.drm_error_unsupported_scheme : R.string.drm_error_unknown;
                Toast.makeText(context, stringId, Toast.LENGTH_LONG).show();
            }
            playerNeedsPrepare = true;
            showControls();
        }

        @Override
        public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
            shutterView.setVisibility(View.GONE);
            videoFrame.setAspectRatio(
                    height == 0 ? 1 : (width * pixelWidthHeightRatio) / height);
        }

        private boolean haveTracks(int type) {
            return player != null && player.getTrackCount(type) > 0;
        }

//        private void toggleControlsVisibility() {
//            if (mediaController.isShowing()) {
//                mediaController.hide();
//            } else {
//                showControls();
//            }
//        }

        private void showControls() {
//            mediaController.show(5000);
            if (player.getPlayerControl().isPlaying())
                btn_play.setVisibility(View.GONE);
            else
                btn_play.setVisibility(View.VISIBLE);

        }

        @Override
        public void onCues(List<Cue> cues) {
            subtitleLayout.setCues(cues);
        }

        @Override
        public void onId3Metadata(Map<String, Object> metadata) {

        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (player != null) {
                player.setSurface(holder.getSurface());
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (player != null) {
                holder.lockCanvas();
                player.blockingClearSurface();
            }
        }

        private void configureSubtitleView() {
            CaptionStyleCompat style;
            float fontScale;
            if (Util.SDK_INT >= 19) {
                style = getUserCaptionStyleV19();
                fontScale = getUserCaptionFontScaleV19();
            } else {
                style = CaptionStyleCompat.DEFAULT;
                fontScale = 1.0f;
            }
            subtitleLayout.setStyle(style);
            subtitleLayout.setFractionalTextSize(SubtitleLayout.DEFAULT_TEXT_SIZE_FRACTION * fontScale);
        }

        @TargetApi(19)
        private float getUserCaptionFontScaleV19() {
            CaptioningManager captioningManager =
                    (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
            return captioningManager.getFontScale();
        }

        @TargetApi(19)
        private CaptionStyleCompat getUserCaptionStyleV19() {
            CaptioningManager captioningManager =
                    (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
            return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
        }

        private int inferContentType(Uri uri, String fileExtension) {
            String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
                    : uri.getLastPathSegment();
            if (lastPathSegment == null) {
                return TYPE_OTHER;
            } else if (lastPathSegment.endsWith(EXT_DASH)) {
                return TYPE_DASH;
            } else if (lastPathSegment.endsWith(EXT_SS)) {
                return TYPE_SS;
            } else if (lastPathSegment.endsWith(EXT_HLS)) {
                return TYPE_HLS;
            } else {
                return TYPE_OTHER;
            }
        }
    }

    public class ProgressBarViewHolder extends RecyclerView.ViewHolder {
        public ProgressBarViewHolder(View view) {
            super(view);
        }
    }

    public void unregisterEventBus() {
        EventBus.getDefault().unregister(this);
    }
}
  • 当item类型为视频时,我创建了一个ExoPlayer实例来播放此视频,并将此实例添加到HashMap中以备后用(键值为该项的位置)。如果视频项正在被回收利用,则释放播放器并将其从HashMap中删除。

  • 一切看起来都很好,但是有一个问题。我假设视频项位于0位置(现在我们可以在此项中看到视频预览)。我向下滑动RecyclerView,只足够隐藏第0项。此时,项目0的VideoViewHolder尚未被回收利用。然后,我向上滚动以显示项目0。视图现在是空白的,没有显示视频预览。我单击项目0以播放视频,播放器仅播放音频(声音),没有显示视频。播放几秒钟后,视频现在才可见。

  • 我进行了调试并发现,在向下滚动以隐藏视频项后,SurfaceView被销毁。当向上滚动以显示视频项时,SurfaceView被创建。我认为这就是VideoViewHolder显示空白视图而不是视频预览的原因。

  • 我的问题是:如何在滚动回视频项后显示视频预览?


你找到任何解决方案了吗?我也有这个问题。 - Hani
我创建了一个新项目,可以帮助所有试图做到这一点的人:https://github.com/paulo-coutinho/rvplayer - Paulo Coutinho
1个回答

2
这是来自ExoPlayer的注释文档

在Android N之前,SurfaceView渲染与视图动画没有正确同步。在早期版本中,这可能会导致SurfaceView放置在滚动容器中时出现不必要的效果,或者当它受到动画影响时,表现为内容稍微滞后于应该显示的位置,以及视图在受到动画影响时变成黑色。

为了在Android N之前实现平滑的视频动画或滚动,因此需要使用TextureView而不是SurfaceView。如果不需要平滑的动画或滚动,则应优先考虑SurfaceView

要启用TextureView surface_type,您必须像下面所示在xml文件中设置它

 <com.google.android.exoplayer2.ui.SimpleExoPlayerView
    android:id="@+id/playerView"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    app:surface_type="texture_view"/>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接