使用Exoplayerstream式video列表

我想实现stream式video列表应用程序。 我用RecyclerView来显示我的列表项。 项目types包括4种types:文章,状态,照片和video。 我们只应该关注videotypes。 这里是我的代码为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); } } 

Solutions Collecting From Web of "使用Exoplayerstream式video列表"

这里是来自ExoPlayer 文档的注释

在Android N之前,SurfaceView渲染与视图animation没有正确的同步。在早期版本中,当SurfaceView被放置在滚动容器中,或者当它受到animation影响时,这可能会导致不希望的效果。 这些效果包括SurfaceView的内容看起来稍微滞后于应该显示的位置,并且在受到animation影响时视图变黑。

为了在Android N之前实现stream畅的animation或video滚动,因此有必要使用TextureView而不是SurfaceView。 如果不需要平滑的animation或滚动,则首选SurfaceView

要启用TextureView ,必须将其设置在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"/>