Vilynx Android SDK Guide v1.3

Android SDK 1.3 changelog

All the changes on the version 1.3 of the SDK were made to improve the overall performance of the SDK, add some new methods and match as possible the Swift SDK. All the apps developed with previous versions should work withoput any trouble, since no breaking changes were made.

Changes on VilynxConstants

VilynxVideoQuality and VilynxImageQuality enums were added to match Swift SDK:

enum class VilynxVideoQuality(val quality: String) {
    VIDEO_PRO69_HIGH_QUALITY("pro69high.viwindow.mp4"),
    VIDEO_PRO69_MID_QUALITY("pro69.viwindow.mp4"),
    VIDEO_PRO69_GALLERY_QUALITY("gallery.pro69.viwindow.mp4"),
    VIDEO_SHORT_QUALITY("short.viwindow.mp4"),
    VIDEO_SHORT_GALLERY_QUALITY("gallery.short.viwindow.mp4")
}

enum class VilynxImageQuality(val quality: String) {
    IMAGE_PRO69_HIGH_QUALITY("pro69high.viwindow0.jpg"),
    IMAGE_PRO69_MID_QUALITY("pro69.viwindow0.jpg"),
    IMAGE_PRO69_GALLERY_QUALITY("gallery.pro69.viwindow0.jpg"),
    IMAGE_SHORT_QUALITY("short.viwindow0.jpg"),
    IMAGE_SHORT_GALLERY_QUALITY ("gallery.short.viwindow0.jpg")
}

Changes on VilynxRequest

Two new variables named videoQuality and imageQuality were added to match Swift SDK behaviour. The constructor now is:

class VilynxRequest @JvmOverloads constructor(var videoId: String,
                                              var title: String,
                                              var subtitle: String,
                                              var videoUrl: String,
                                              var videoQuality: VilynxVideoQuality? = null,
                                              var imageQuality: VilynxImageQuality? = null)

Note that both new variables are optionals, so it will not break your previous code.

Changes on requesting VilynxResponses

A new method getVilynxResponses(requests: ArrayList<VilynxRequest>, frame: VilynxFrame?, callback: VilynxListener) was added to match the Swift SDK behaviour.

The second parameter is a VilynxFrame object, which is used to represent the width and height of the view where the player will go. Based on the frame, the SDK will choose the best video quality for it. The frame object will only be used by the SDK if the videoQuality and imageQuality properties of the VilynxRequest are not set. Note that frame or both image and video qualities must be set, or the SDK will assert.

An example usage of getVilyxnResponses:

class ... {

    private var listVilynxResponse = java.util.ArrayList()
    ...
    private fun startVilynx() {

        val requests = java.util.ArrayList<VilynxRequest>()
        requests.add(VilynxRequest("vid-1", "Ad Paco Rabanne - Million Crazy Dance", "Subtitle 1", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
        requests.add(VilynxRequest("vid-2", "Economic Impact of Immigrants", "Subtitle 2", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
        requests.add(VilynxRequest("vid-4", "Mike Tittel BTS - Tennis", "Subtitle 4", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-5", "MTV Video Music Awards 2016", "Subtitle 5", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-6", "The Real Thing", "Subtitle 6", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-7", "Underground Football", "Subtitle 7", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))

        manager.getVilynxResponses(requests, VilynxFrame(100.0, 500.0), object: VilynxManager.VilynxListener {
            override fun callbackErrorResponse(error: String) {

            }

            override fun callbackPlayerUrlResponse(response: ArrayList<VilynxResponse>) {

                listVilynxResponse = response

            }
        })
    }
}

The detailed implementation can be found on the SDK sample projects.

Initial setup

From an Activity or a Fragment you need to instantiate a VilynxManager by passing the Activity or Fragment reference and your public-key, which can be obtained from your Vilynx Dashboard:

VilynxManager vilynxManager = new VilynxManager(this, "public-key");

Vilynx Android SDK comes with built-in cache for video resources.

1. Requesting previews data

Once you have the VilynxManager instantiated, to request a set of videos and thumbnails you need to create an array of VilynxResponse items.

Using getVilynxResponses

class ... {

    // variable to hold the response objects
    private var listVilynxResponse = java.util.ArrayList()
    ...
    private fun startVilynx() {

        val requests = java.util.ArrayList<VilynxRequest>()
        requests.add(VilynxRequest("vid-1", "Ad Paco Rabanne - Million Crazy Dance", "Subtitle 1", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
        requests.add(VilynxRequest("vid-2", "Economic Impact of Immigrants", "Subtitle 2", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
        requests.add(VilynxRequest("vid-4", "Mike Tittel BTS - Tennis", "Subtitle 4", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-5", "MTV Video Music Awards 2016", "Subtitle 5", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-6", "The Real Thing", "Subtitle 6", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-7", "Underground Football", "Subtitle 7", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
        requests.add(VilynxRequest("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))

        // If the frame is set, it will be used to automatically calculate the best video resolution for it
        manager.getVilynxResponses(requests, VilynxFrame(100.0, 500.0), object: VilynxManager.VilynxListener {
            override fun callbackErrorResponse(error: String) {

            }

            override fun callbackPlayerUrlResponse(response: ArrayList<VilynxResponse>) {
                // Assign the response to our class variable
                listVilynxResponse = response

            }
        })
    }
}

You can also manually set the quality of the videos:

    // The last 2 parameters of VilynxRequest are the video and image quality.
    requests.add(VilynxRequest("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4", VilynxVideoQuality.VIDEO_PRO69_GALLERY_QUALITY, VilynxImageQuality.IMAGE_PRO69_GALLERY_QUALITY))

    // Pass a null frame to ignore the parameter
    manager.getVilynxResponses(requests, null, object: VilynxManager.VilynxListener {

Using bulk request (DEPRECATED)

getVilynxBulkUrls(listVilynxResponse: ArrayList<VilynxResponse>, videoQuality: String, imageQuality: String) returns the Vilynx videos and thumbnails urls on it's response.

Example

The Activity or Fragment needs to implement the VilynxListener interface. Example:

public class MainActivity extends AppCompatActivity implements VilynxManager.VilynxListener {
  private ArrayList<VilynxResponse> listVilynxResponse
  ...
  @Override
  public void callbackErrorResponse(String error) {
      Log.e(TAG, "ERROR : " + error);
  }

  @Override
  public void callbackPlayerUrlResponse(ArrayList<VilynxResponse> response) {
      this.listVilynxResponse = response;
  }
  ...
}

For requesting a set of videos and thumbnails you need to add VilynxResponse items (with your correspondign video id's, in the example called vid-1, vid-2...) to the listVilynxResponse array and call getVilynxBulkUrls(passing listVilynxResponse, video quality and image quality):

listVilynxResponse.add(new VilynxResponse("vid-1", "Ad Paco Rabanne - Million Crazy Dance", "Subtitle 1", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-2", "Economic Impact of Immigrants", "Subtitle 2", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-4", "Mike Tittel BTS - Tennis", "Subtitle 4", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-5", "MTV Video Music Awards 2016", "Subtitle 5", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-6", "The Real Thing", "Subtitle 6", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-7", "Underground Football", "Subtitle 7", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
listVilynxResponse.add(new VilynxResponse("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));

vilynxManager.getVilynxBulkUrls(listVilynxResponse, VilynxConstants.VIDEO_PRO69_GALLERY_QUALITY, VilynxConstants.IMAGE_PRO69_GALLERY_QUALITY);

When all the requests are completed the callbackPlayerUrlResponse() interface method will be called:

@Override
public void callbackPlayerUrlResponse(ArrayList<VilynxResponse> response) {
    this.listVilynxResponse = response;
}

2. Playing videos inside a RecyclerView

If you plan to play a video on a scrollable view, you should not use the Android VideoView, since it does not have synchronization buffers and your scroll will be slowed down.

We recommend you to use:

If you use ExoPlayer, set the ExoPlayerView surface_type of your xml layout to texture_view:

<com.google.android.exoplayer2.ui.PlayerView app:surface_type="texture_view"/>

Also you can disable the ExoPlayerView built-in video controllers by adding the following line:

app:use_controller="false"

Example using Exoplayer and calculating most visible video

The layout for our RecyclerView items will be:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <android.support.v7.widget.CardView xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/summary_card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="blocksDescendants"
        app:cardElevation="8dp"
        app:cardUseCompatPadding="true">


        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/vilynx_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="24dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="16dp"
                android:text="Large Title to test how will work with this thing here because yes"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/vilynx_img" />

            <com.google.android.exoplayer2.ui.PlayerView
                android:id="@+id/vilynx_video"
                android:layout_width="0dp"
                android:layout_height="220dp"
                android:background="@android:color/background_light"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:resize_mode="zoom"
                app:surface_type="texture_view"
                app:use_controller="false" />

            <ImageView
                android:id="@+id/vilynx_img"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                android:layout_gravity="center_horizontal"
                android:background="@android:color/background_light"
                android:contentDescription="@string/app_name"
                android:scaleType="centerCrop"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </android.support.constraint.ConstraintLayout>

    </android.support.v7.widget.CardView>
</LinearLayout>

On our MainActivity layout we will have a RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/vilynx_recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="16dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Connection not available"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/vilynx_recycler_view" />
</android.support.constraint.ConstraintLayout>

On our MainActivity class:

class MainActivity : AppCompatActivity(), VilynxManager.VilynxListener {
    // UI elements
    private var recyclerView: RecyclerView? = null
    private var adapter: VilynxAdapter? = null
    private var layoutManager: LinearLayoutManager? = null

    // Data elements
    private var listVilynxResponse = java.util.ArrayList<VilynxResponse>()
    // An array of Players is stored in order to Play/Pause them when the visible items change
    private var players: ArrayList<SimpleExoPlayer> = ArrayList()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        manager = VilynxManager(this, "a891241f47eeab5efc5bdfc3ef540f45")
        setContentView(R.layout.activity_main)
        startVilynx()
    }

    // Call the VilynxManager with some videos usin VilynxRequest instances
    private fun startVilynx() {
            val requests = java.util.ArrayList<VilynxRequest>()
            listVilynxResponse.clear()
            requests.add(VilynxRequest("vid-1", "Ad Paco Rabanne - Million Crazy Dance", "Subtitle 1", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
            requests.add(VilynxRequest("vid-2", "Economic Impact of Immigrants", "Subtitle 2", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"))
            requests.add(VilynxRequest("vid-4", "Mike Tittel BTS - Tennis", "Subtitle 4", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
            requests.add(VilynxRequest("vid-5", "MTV Video Music Awards 2016", "Subtitle 5", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
            requests.add(VilynxRequest("vid-6", "The Real Thing", "Subtitle 6", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
            requests.add(VilynxRequest("vid-7", "Underground Football", "Subtitle 7", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
            requests.add(VilynxRequest("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"))
            manager.getVilynxBulkUrls(requests, VilynxConstants.VIDEO_PRO69_GALLERY_QUALITY, VilynxConstants.IMAGE_PRO69_GALLERY_QUALITY)
        }

    // Once the request are completed the interface method callbackPlayerUrlResponse is called
    override fun callbackPlayerUrlResponse(response: ArrayList<VilynxResponse>) {
        this.listVilynxResponse = response
        for (item in response) {
                // Build the players objects
            createExoPlayer()
        }
        // Once everything is done we can configure our RecyclerView
        configureRecyclerView()
    }

    private fun createExoPlayer() {
        val player = manager.getExoPlayer()
        players.add(player)
    }

    private fun configureRecyclerView() {
         // Initialize the layout manager, adapter...
        layoutManager = LinearLayoutManager(this)
        adapter = VilynxAdapter(listVilynxResponse, players, this)
        recyclerView = findViewById(R.id.vilynx_recycler_view)
        recyclerView?.layoutManager = layoutManager
        recyclerView?.adapter = adapter

         // Add the OnScrollListener. This will be used to trigger events that will calculate the most visible element at a moment
        recyclerView?.addOnScrollListener(object: RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
                if (!recyclerView?.canScrollVertically(1)!!) {
                       // only called when user scrolled to the bottom
                    playLastVideo()
                } else {
                    calculateVideoToPlay()
                }
            }
        })
        // Start playing the first video
        players[0].playWhenReady = true
    }

    private fun playVideoAtIndex(i: Int) {
        if (i != -1) { // -1 is an invalid scroll event
            // Set playWhenReady to play the video when ready
            players[i].playWhenReady = true
            // If the player media was not loaded then load it
            // Avoid loading all the players media at once and load it on demand
            if (players[i].playbackState == Player.STATE_IDLE) {
                buildPlayerData(players[i], listVilynxResponse[i])
            }
            // Pause all the other players
            players.filterIndexed { index, _ -> index != i }
                    .map { it.playWhenReady = false }
        }
    }

    private fun buildPlayerData(player: SimpleExoPlayer, vilynxResponse: VilynxResponse) {
        manager.buildPlayerData(player, vilynxResponse.getVideoPreviewUrl())
    }

}

Also you can handle the onResume() and onPause() methods to play/pause the videos:

public override fun onResume() {
    super.onResume()
    if (players.size > 0) {
         // Check the RecyclerView is initialized
        if (recyclerView != null && recyclerView?.canScrollVertically(1)!!) {
              // If it is not at the bottom
            calculateVideoToPlay()
        } else {
              // Otherwise
            playLastVideo()
        }
    }
}

public override fun onPause() {
    unregisterReceiver(networkStateReceiver)
    players.map { it.playWhenReady = false } // Pause all the videos
    super.onPause()
}

The adapter for the RecyclerView is:

public class VilynxAdapter extends RecyclerView.Adapter <VilynxAdapter.VilynxViewHolder>{

    private ArrayList<VilynxResponse> items;
    private ArrayList<SimpleExoPlayer> players;
    private Context context;

    VilynxAdapter (ArrayList<VilynxResponse> items, ArrayList<SimpleExoPlayer> players, Context context) {
        this.items = items;
        this.players = players;
        this.context = context;
    }

    @NonNull
    @Override
    public VilynxViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_video_item, viewGroup, false);
        return new VilynxViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull VilynxViewHolder vilynxViewHolder, int i) {
        vilynxViewHolder.setItem(items.get(i), players.get(i));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }
}

And the ViewHolder:

VilynxViewHolder extends RecyclerView.ViewHolder {
          // The UI elements of the holder
        TextView videoTitle;
        ImageView imageCover;
        PlayerView playerView;

        VilynxViewHolder(@NonNull View itemView) {
            super(itemView);

            videoTitle = itemView.findViewById(R.id.vilynx_title);
            imageCover = itemView.findViewById(R.id.vilynx_img);
            playerView = itemView.findViewById(R.id.vilynx_video);
            imageCover.setAlpha(1.0f);
        }

        void showImage() {
              // Only if image was previusly hidden (avoid triggering multiple events)
            if (imageCover.getAlpha() == 0.0) {
                imageCover.animate().alpha(1.0f).setDuration(200).withEndAction(new Runnable() {
                    @Override
                    public void run() {
                         // Once the animation is run, seek the player to 0
                        playerView.getPlayer().seekTo(0);
                    }
                }).start();
            }
        }

        void hideImage() {
              // just hide the image with no animation, the video will play
            imageCover.setAlpha(0.0f);
        }

        void setItem(VilynxResponse response, Player player) {
            videoTitle.setText(response.getTitle());
            // Associate the player with the PlayerView
            playerView.setPlayer(player);
            // Load the thumbnail image into the ImageView    
            Picasso.get().load(response.getThumbnailUrl()).into(imageCover);
            // Listener for the player events
            player.addListener(new Player.EventListener() {
                @Override
                public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {

                }

                @Override
                public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

                }

                @Override
                public void onLoadingChanged(boolean isLoading) {

                }

                   // The video will start playing when playWhenReady is true and playbackState == Player.STATE_READY. We will hide the image then
                   // If playWhenReady is false the player will be paused and we will show the cover image
                @Override
                public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                    if (playWhenReady &amp;&amp; playbackState == Player.STATE_READY) {
                        hideImage();
                    } else if (!playWhenReady) {
                        showImage();
                    }
                }

                @Override
                public void onRepeatModeChanged(int repeatMode) {

                }

                @Override
                public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

                }

                @Override
                public void onPlayerError(ExoPlaybackException error) {

                }

                @Override
                public void onPositionDiscontinuity(int reason) {

                }

                @Override
                public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

                }

                @Override
                public void onSeekProcessed() {

                }
            });
            playerView.requestFocus();
        }
    }

Example using toro and ExoPlayer

First you need to include Toro and ExoPlayer dependencies on your app gradle file:

implementation "im.ene.toro3:toro:3.5.2"
implementation "im.ene.toro3:toro-ext-exoplayer:3.5.2"
implementation "com.google.android.exoplayer:exoplayer:2.7.3"

Add a Toro container on the activity or fragment you would like to show the videos:

<im.ene.toro.widget.Container
android:id="@+id/player_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

Create class for representing the items (videoPreviewUrl, imageUrl...):

public class SummaryItem {
    private final String mTitle;
    private String vilynxVideoUrl;
    private String videoUrl;
    private final String mImageResource;
    private ImageView vilynxImg;

    public SummaryItem(String title, String imageResource, String vilynxVideoUrl, String videoUrl) {
        this.mTitle = title;
        this.mImageResource = imageResource;
        this.vilynxVideoUrl = vilynxVideoUrl;
        this.videoUrl = videoUrl;
    }

    @Override
    public String toString() {
        return getClass() + ", mTitle[" + mTitle + "]";
    }

    public String getVideoUrl() {
        return videoUrl;
    }

    public void setVilynxImg(ImageView vilynxImg) {
        this.vilynxImg = vilynxImg;
    }

    public String getTitle() {
        return mTitle;
    }

    public String getImageResource() {
        return mImageResource;
    }

    public String getVilynxVideoUrl() {
        return vilynxVideoUrl;
    }
}

Load Vilynx url resources using the SDK and use it to create an array of the model previusly created:

private void startVilynx(){
    try {
        VilynxManager vilynxManager = new VilynxManager(this, "a891241f47eeab5efc5bdfc3ef540f45");
        listVilynxResponse.clear();
        listVilynxResponse.add(new VilynxResponse("vid-1", "Ad Paco Rabanne - Million Crazy Dance", "Subtitle 1", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-2", "Economic Impact of Immigrants", "Subtitle 2", "http://mirrors.standaloneinstaller.com/video-sample/Panasonic_HDC_TM_700_P_50i.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-4", "Mike Tittel BTS - Tennis", "Subtitle 4", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-5", "MTV Video Music Awards 2016", "Subtitle 5", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-6", "The Real Thing", "Subtitle 6", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-7", "Underground Football", "Subtitle 7", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
        listVilynxResponse.add(new VilynxResponse("vid-8", "Viz Libero Football", "Subtitle 8", "http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4"));
        vilynxManager.getVilynxBulkUrls(listVilynxResponse, VilynxConstants.VIDEO_PRO69_GALLERY_QUALITY, VilynxConstants.IMAGE_PRO69_GALLERY_QUALITY);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public ArrayList generateTestList() {
    ArrayList summariesArray = new ArrayList();

    for(int i = 0; i < listVilynxResponse.size(); i++) {
        VilynxResponse responseItem = listVilynxResponse.get(i);

        SummaryItem summaryItem = new SummaryItem(responseItem.getTitle(),
        responseItem.getThumbnailUrl(),
        responseItem.getVideoPreviewUrl(),
        responseItem.getVideoUrl());

        summariesArray.add(summaryItem);
    }

    return summariesArray;
}

Create a layout for the items of the RecyclerView (in this example it is named summary_layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <android.support.v7.widget.CardView xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/summary_card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/border"
        android:descendantFocusability="blocksDescendants"
        app:cardElevation="8dp"
        app:cardUseCompatPadding="true">


        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/vilynx_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="24dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="16dp"
                android:text="Large Title to test how will work with this thing here because yes"
                android:textColor="@color/black"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/vilynx_img" />

            <com.google.android.exoplayer2.ui.PlayerView
                android:id="@+id/vilynx_video"
                android:layout_width="0dp"
                android:layout_height="220dp"
                android:background="@android:color/background_light"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:resize_mode="zoom"
                app:surface_type="texture_view"
                app:use_controller="false" />

            <ImageView
                android:id="@+id/vilynx_img"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                android:layout_gravity="center_horizontal"
                android:background="@android:color/background_light"
                android:contentDescription="@string/app_name"
                android:scaleType="centerCrop"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </android.support.constraint.ConstraintLayout>

    </android.support.v7.widget.CardView>
</LinearLayout>

Create a ViewHolder for your items:

public class VideoViewHolder extends RecyclerView.ViewHolder implements ToroPlayer {
    // UI
    private PlayerView playerView;
    private CardView cardView;
    private final TextView mTitle;
    private final ImageView mCover;

    // Private class atributes
    private ExoPlayerViewHelper helper; // the helper is used for controlling the player (play/pause/release...)
    private Uri mediaUri;

    // ViewHolder constructor
    // The delegate is used to handle the Click action and pass it back to the activity
    VideoViewHolder(View view, RecyclerViewClickDelegate delegate) {
        super(view);
        playerView = view.findViewById(R.id.vilynx_video);
        mTitle = view.findViewById(R.id.vilynx_title);
        mCover = view.findViewById(R.id.vilynx_img);
        cardView = view.findViewById(R.id.summary_card_view);
        cardView.setOnClickListener(v -> delegate.didSelectedRow(getAdapterPosition()));
    }

    // Called from the adapter onBindViewHolder
    void setItem(SummaryItem item) {
        mTitle.setText(item.getTitle());
        // Load image from url using Picasso for caching
        Picasso.get().load(item.getImageResource()).into(mCover);
        // Assign the mediaUri from the vilynxVideoUrl of the item
        mediaUri = Uri.parse(item.getVilynxVideoUrl());
    }

    // Called when we want to hide the cover ImageView
    private void hideImage() {
        mCover.animate().setDuration(400).setStartDelay(0).alpha(0).start();
    }

    // Called when we want to show the cover ImageView
    private void showImage() {
        mCover.animate().setDuration(400).setStartDelay(0).alpha(1).start();
    }

    //+++++++ ToroPlayer Implementation +++++++
    // Return the player view
    @NonNull @Override public View getPlayerView() {
        return playerView;
    }

    @NonNull @Override public PlaybackInfo getCurrentPlaybackInfo() {
        return helper != null ? helper.getLatestPlaybackInfo() : new PlaybackInfo();
    }

    // Initialization of the helper (if was null) and the player
    @Override
    public void initialize(@NonNull Container container, @Nullable PlaybackInfo playbackInfo) {
        if (helper == null) {
            helper = new ExoPlayerViewHelper(this, mediaUri);
        }
        helper.initialize(container, playbackInfo);
        // Get the playerView player and set it's repeat mode to all for looping the video
        playerView.getPlayer().setRepeatMode(Player.REPEAT_MODE_ALL);
        // Helper event listener. We want to listen for the onPlayerStateChanged() event to check if the video started playing and hide our cover ImageView
        helper.addEventListener(new Playable.EventListener() {
            @Override
            public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {

            }

            @Override
            public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

            }

            @Override
            public void onLoadingChanged(boolean isLoading) {

            }

            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                // If the video is playing, hide the cover ImageView
                if (playbackState == Player.STATE_READY) {
                    hideImage();
                }
            }

            @Override
            public void onRepeatModeChanged(int repeatMode) {

            }

            @Override
            public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

            }

            @Override
            public void onPlayerError(ExoPlaybackException error) {

            }

            @Override
            public void onPositionDiscontinuity(int reason) {

            }

            @Override
            public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

            }

            @Override
            public void onSeekProcessed() {

            }

            @Override
            public void onMetadata(Metadata metadata) {

            }

            @Override
            public void onCues(List cues) {

            }

            @Override
            public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {

            }

            @Override
            public void onRenderedFirstFrame() {

            }
        });
        // Mute the video
        helper.setVolume(0.0f);
    }

    // Free-up resources
    @Override public void release() {
        if (helper != null) {
            helper.release();
            helper = null;
        }
        // The video is no longer here, show the cover ImageView
        showImage();
    }

    // Toro calls this when the ViewHolder at a given position should play the video
    // We call helper.play() to start playing and also seekTo(0) the video to always start from the beginning
    @Override public void play() {
        if (helper != null) helper.play();
        playerView.getPlayer().seekTo(0);
    }

    // Called when Toro wants to pause a video
    // We show the cover ImageView
    @Override public void pause() {
        if (helper != null) {
            helper.pause();
        }
        showImage();
    }

    @Override public boolean isPlaying() {
        return helper != null && helper.isPlaying();
    }

    // This defines when a element should we played or not
    // With this implemenentation, the element will be played if it has more of 85% of visible area.
    @Override public boolean wantsToPlay() {
        return ToroUtil.visibleAreaOffset(this, itemView.getParent()) >= .85;
    }

    @Override public int getPlayerOrder() {
        return getAdapterPosition();
    }
}

Create an adapter:

public class VideoAdapter extends RecyclerView.Adapter<VideoViewHolder> {

    private final ArrayList<SummaryItem> mList;
    private RecyclerViewClickDelegate delegate;

    VideoAdapter(ArrayList<SummaryItem> list, RecyclerViewClickDelegate delegate){
        mList = list;
        this.delegate = delegate;
    }

    @Override
    public VideoViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
        View view = LayoutInflater.from(viewGroup.getContext())
        .inflate(R.layout.summary_layout, viewGroup, false);
        return new VideoViewHolder(view, delegate);
    }

    @Override
    public void onBindViewHolder(VideoViewHolder viewHolder, int position) {
        SummaryItem videoItem = mList.get(position);
        viewHolder.setItem(videoItem);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

Configure the player on your activity onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    container = findViewById(R.id.player_container);
    layoutManager = new LinearLayoutManager(this);
    container.setLayoutManager(layoutManager);
    adapter = new VideoAdapter(mList, this);
    container.setAdapter(adapter);
}

3. VilynxConstants

The SDK comes with a set of predefined constants that defines the resolution of video and image resources. Those constants are:

const val VIDEO_PRO69_HIGH_QUALITY      = "pro69high.viwindow.mp4"
const val VIDEO_PRO69_MID_QUALITY       = "pro69.viwindow.mp4"
const val VIDEO_PRO69_GALLERY_QUALITY   = "gallery.pro69.viwindow.mp4"
const val VIDEO_SHORT_QUALITY           = "short.viwindow.mp4"
const val VIDEO_SHORT_GALLERY_QUALITY   = "gallery.short.viwindow.mp4"

const val IMAGE_PRO69_HIGH_QUALITY      = "pro69high.viwindow0.jpg"
const val IMAGE_PRO69_MID_QUALITY       = "pro69.viwindow0.jpg"
const val IMAGE_PRO69_GALLERY_QUALITY   = "gallery.pro69.viwindow0.jpg"
const val IMAGE_SHORT_QUALITY           = "short.viwindow0.jpg"
const val IMAGE_SHORT_GALLERY_QUALITY   = "gallery.short.viwindow0.jpg"

The resolutions associated are:

  • PRO69_HIGH: 1280x720
  • PRO69_MID: 640x360
  • PRO69_GALLERY: 360x202

PRO69 stands for 16:9 aspect ratio.

Download SDK + Example

To get access to our SDKs, please contact us at info@vilynx.com.