Skip to content

7. Recipes

Nam Nguyen Hoai edited this page Oct 25, 2016 · 16 revisions

Below are some tutorials to help users to implement their own components

Menu
7.1 Custom Playback Strategy
7.2 Custom Extension
7.3 Custom ViewHolder
7.4 Custom MediaPlayerManager
7.5 Custom LayoutManager (to use with Toro)

7.1 Custom Playback Strategy

By implementing the interface ToroStrategy, apply your own logic in required methods, you can have your own Strategy. Built-in ones can be used as delegation. See below for a sample:

Least dependency

compile "im.ene.toro2:toro:2.1.0"
public class PlayFromTopStrategy implements ToroStrategy {

  private final int firstMediaPosition; // Should be the first Media obtained item from Adapter

  private boolean isFirstVideoObserved = false;
  private final ToroStrategy delegate = Toro.Strategies.FIRST_PLAYABLE_TOP_DOWN;

  public PlayFromTopStrategy(int firstMediaPosition) {
    this.firstMediaPosition = firstMediaPosition;
  }

  @Override public String getDescription() {
    return "All Media should be played by order, start from 'firstMediaPosition'";
  }

  @Nullable @Override public ToroPlayer findBestPlayer(List<ToroPlayer> candidates) {
    // Inherit the same behaviour with built-in one.
    return delegate.findBestPlayer(candidates);
  }

  @Override public boolean allowsToPlay(ToroPlayer player, ViewParent parent) {
    boolean allowToPlay = (isFirstVideoObserved || player.getPlayOrder() == firstMediaPosition)  //
        && delegate.allowsToPlay(player, parent);
    // Keep track of first video on top.
    if (player.getPlayOrder() == firstMediaPosition) {
      isFirstVideoObserved = true;
    }
    return allowToPlay;
  }
}

And use it as usual:

Toro.setStrategy(new PlayFromTopStrategy(2)); // I know that the 3rd item is a Video.

7.2 Create a Custom Extension

Here is the tutorial about how to create your own Toro Extension.

As can be seen from the library structure, each extension is implemented in a separated way, which means that anyone can do the same thing. The following is a check list for those when building their own Extension for Toro:

  • A good motivation: built-in Extensions are not enough, there are better Media Playback APIs out there, ...

  • When you have a good motivation, now build your own PlayerView - a custom View which can be used to display the Media Content (well, just another VideoView, or SimpleExoPlayerView, ...). If you build your Extension on top of built-in one, it is good to use my built-in Views, or customize it by your own will.

  • Create the extension of PlayerViewHelper: as said, this class stays between the ViewHolder, the Media Playback API and Toro's internal API. A ViewHolder will require it to connect to all benefit of Toro as well as the Playback API. My built-in Helpers can be re-used as delegation as well.

  • Create the ViewHolder: this is the main character. You will need to create a ViewHolder and setup its PlayerViewHelper instance there, to connect your ViewHolder with your Playback API as well as Toro internal API.

  • Optional: extends the MediaPlayerManager. This can be useful when you need to have access to the Manager, or you have better way to handle the position cache (use native cache, maybe). See TimelineAdapter.java to see how I create a simple one.

7.3 Custom ViewHolder

It is recommended that user implements Custom ViewHolder by extending ToroAdapter$$ViewHolder and implementing ToroPlayer or even ExtToroPlayer. Using built-in extensions, it will be more convenient. User can just extend built-in Base ViewHolder implementation. Below is an example, extract from Facebook Sample, shows you how to create one:

Least dependency

compile "im.ene.toro2:toro-ext-exoplayer2:${toroVersion}"

ViewHolder's itemView layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

  <View
      android:layout_width="match_parent"
      android:layout_height="@dimen/activity_vertical_margin"
      android:background="#e5e5e5"
      />

  <include layout="@layout/vh_fb_feed_user"/>

  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#f5f5f5"
      android:lineSpacingMultiplier="1.2"
      android:padding="8dp"
      android:text="@string/sample"
      android:textAppearance="@style/TextAppearance.AppCompat.Body1"
      />

  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="4dp"
      android:background="#20000000"
      android:padding="4dp"
      >

    <im.ene.toro.exoplayer2.ExoVideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        />

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignBottom="@id/video"
        android:layout_alignEnd="@id/video"
        android:layout_alignLeft="@id/video"
        android:layout_alignRight="@id/video"
        android:layout_alignStart="@id/video"
        android:layout_alignTop="@id/video"
        android:layout_centerInParent="true"
        android:background="#40ffffff"
        android:padding="16dp"
        android:scaleType="centerInside"
        />

    <TextView
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_margin="4dp"
        android:background="#80000000"
        android:gravity="center"
        android:includeFontPadding="false"
        android:paddingBottom="4dp"
        android:paddingEnd="8dp"
        android:paddingStart="8dp"
        android:paddingTop="4dp"
        android:maxLines="1"
        android:textAppearance="@style/TextAppearance.AppCompat.Small"
        android:textColor="@android:color/white"
        />

  </RelativeLayout>

  <View
      android:layout_width="match_parent"
      android:layout_height="0.5dp"
      android:layout_marginLeft="24dp"
      android:layout_marginRight="24dp"
      android:background="#e5e5e5"
      />

  <!-- external widget -->
  <include layout="@layout/vh_fb_feed_bottom"/>
</LinearLayout>

Preview

Image here

public class VideoViewHolder extends ExoVideoViewHolder {

  static final int LAYOUT_RES = R.layout.vh_fb_feed_post_video;

  private TimelineItem.VideoItem videoItem;
  private ImageView mThumbnail;
  private TextView mInfo;

  public VideoViewHolder(View itemView) {
    super(itemView);
    mThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
    mInfo = (TextView) itemView.findViewById(R.id.info);
  }

  @Override protected ExoVideoView findVideoView(View itemView) {
    return (ExoVideoView) itemView.findViewById(R.id.video);
  }

  @Override public void bind(RecyclerView.Adapter adapter, @Nullable Object object) {
    if (!(object instanceof TimelineItem)
        || !(((TimelineItem) object).getEmbedItem() instanceof TimelineItem.VideoItem)) {
      throw new IllegalArgumentException("Only VideoItem is accepted");
    }

    this.videoItem = (TimelineItem.VideoItem) ((TimelineItem) object).getEmbedItem();
    this.videoView.setMedia(Uri.parse(videoItem.getVideoUrl()));
  }

  @Override public void setOnItemClickListener(View.OnClickListener listener) {
    super.setOnItemClickListener(listener);
    mInfo.setOnClickListener(listener);
    this.videoView.setOnClickListener(listener);
  }

  @Nullable @Override public String getMediaId() {
    return Util.genVideoId(this.videoItem.getVideoUrl(), getAdapterPosition());
  }

  @Override public void onVideoPreparing() {
    super.onVideoPreparing();
    mInfo.setText("Preparing");
  }

  @Override public void onVideoPrepared() {
    super.onVideoPrepared();
    mInfo.setText("Prepared");
  }

  @Override public void onViewHolderBound() {
    super.onViewHolderBound();
    Picasso.with(itemView.getContext())
        .load(R.drawable.toro_place_holder)
        .fit()
        .centerInside()
        .into(mThumbnail);
    mInfo.setText("Bound");
  }

  @Override public void onPlaybackStarted() {
    mThumbnail.animate().alpha(0.f).setDuration(250).setListener(new AnimatorListenerAdapter() {
      @Override public void onAnimationEnd(Animator animation) {
        VideoViewHolder.super.onPlaybackStarted();
      }
    }).start();
    mInfo.setText("Started");
  }

  @Override public void onPlaybackPaused() {
    mThumbnail.animate().alpha(1.f).setDuration(250).setListener(new AnimatorListenerAdapter() {
      @Override public void onAnimationEnd(Animator animation) {
        VideoViewHolder.super.onPlaybackPaused();
      }
    }).start();
    mInfo.setText("Paused");
  }

  @Override public void onPlaybackCompleted() {
    mThumbnail.animate().alpha(1.f).setDuration(250).setListener(new AnimatorListenerAdapter() {
      @Override public void onAnimationEnd(Animator animation) {
        VideoViewHolder.super.onPlaybackCompleted();
      }
    }).start();
    mInfo.setText("Completed");
  }

  @Override public boolean onPlaybackError(Exception error) {
    mThumbnail.animate().alpha(1.f).setDuration(0).setListener(new AnimatorListenerAdapter() {
      @Override public void onAnimationEnd(Animator animation) {
        // Immediately finish the animation.
      }
    }).start();
    mInfo.setText("Error: videoId = " + getMediaId());
    return super.onPlaybackError(error);
  }
}

7.4 Custom MediaPlayerManager

7.5 Custom LayoutManager (to use with Toro)

Clone this wiki locally