Autoplayable RecyclerView Items

ARVI will enable you to make your feeds more interactive and appealing to your end users without the need to spend a lot of your valuable time on the implementation.

ARVI

Getting Started

Prerequisites

1. Make sure that you've added the jcenter() repository to your top-level build.gradle file.

buildscript {
    //...
    repositories {
        //...
        jcenter()
    }
    //...
}

2. Enable the jetifier and androidX support in the top-level gradle.properties file.

//...
android.enableJetifier=true
android.useAndroidX=true
//....

3. Update your compileSdkVersion in the module-level build.gradle file to 28+.

//...
android {
    //...
    compileSdkVersion 28
    //...
}
//...

4. Replace your com.android.support.appcompat.* dependency with the new androidx.appcompat.* alternative.

//...
dependencies {
    //...
    implementation "androidx.appcompat:appcompat:1.0.1"
    //...
}
//...

5. Add the ExoPlayer dependency to the module-level build.gradle file.

//...
dependencies {
    //...
    implementation "com.google.android.exoplayer:exoplayer:2.9.2"
    //...
}
//...

ARVI Dependencies

ARVI is comprised of several library modules, namely:

  • arvi - core functionality (Required)
  • arvi-adapster - Adapster adaptation (Optional)
  • arvi-ktx - common extensions (Optional)
  • arvi-utils - common utils and helpers (Optional)

The basic implementation would have to include the core module

Latest version: Download

implementation "com.arthurivanets.arvi:arvi:X.Y.Z"

Which should be added to your module-level build.gradle file.

ext {
    //...
    arviLibraryVersion = "1.0.0"
}

dependencies {
    //...
    implementation "com.arthurivanets.arvi:arvi:$arviLibraryVersion"
}

After that you can proceed with further implementation.

See: Basic Implementation and Adapster-based Implementation

Basic Implementation

Basic implementation consists of 3 straightforward steps, which include the proper handling of the system memory claims, creation of the playable Item View Holder, and the incorporation of the Playable Items Container.

The steps you need to take:

1. Ensure the proper release of the active players when the application goes into background (System Memory Claims)

Kotlin (click to expand)

Basic

//...
import com.arthurivanets.arvi.PlayerProviderImpl

class ArviApplication : Application() {

    //...

    override fun onTrimMemory(level : Int) {
        super.onTrimMemory(level)

        if(level >= TRIM_MEMORY_BACKGROUND) {
            PlayerProviderImpl.getInstance(this).release()
        }
    }

    //...

}

With arvi-ktx

//...
import com.arthurivanets.arvi.ktx.playerProvider

class ArviApplication : Application() {

    //...

    override fun onTrimMemory(level : Int) {
        super.onTrimMemory(level)

        if(level >= TRIM_MEMORY_BACKGROUND) {
            playerProvider.release()
        }
    }

    //...

}


Java (click to expand)

//...
import com.arthurivanets.arvi.PlayerProviderImpl;

public final class YourApplication extends Application {

    //...

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);

        if(level >= TRIM_MEMORY_BACKGROUND) {
            PlayerProviderImpl.getInstance(this).release();
        }
    }
    
    //...

}


2. Implement your Item's ViewHolder based on the PlayableItemViewHolder

IMPORTANT: Your ViewHolder's layout.xml file must contain a PlayerView child with an id @id/player_view.

See: BasicVideoItemViewHolder and item_video.xml

Kotlin (click to expand)

class BasicVideoItemViewHolder(
    parent : ViewGroup,
    itemView : View
) : PlayableItemViewHolder(parent, itemView) {

    //...

    override fun getUrl() : String {
        return "video_url..."
    }
    
    //...

}


Java (click to expand)

public final class BasicVideoItemViewHolder extends PlayableItemViewHolder {

    //...

    @Override
    public final String getUrl() {
        return "video_url...";
    }

    //...

}


3. Replace the regular RecyclerView with the PlayableItemsRecyclerView

IMPORTANT: PlayableItemsRecyclerView should be bound to the lifecycle of the Activity/Fragment (Activity/Fragment lifecycle events should be propagated to the PlayableItemsRecyclerView) in order to ensure the correct handling of the item video playback.

See: PlayableItemsRecyclerView, BasicVideoItemsRecyclerViewAdapter, BasicVideosFragment and fragment_videos.xml

Kotlin (click to expand)

class BasicVideosFragment : BaseFragment() {

    //...

    override fun init(savedInstanceState : Bundle?) {
        with(recyclerView) {
	    // PlayableItemRecyclerView configuration
            setPlaybackTriggeringStates(
                PlayableItemsContainer.PlaybackTriggeringState.IDLING,
                PlayableItemsContainer.PlaybackTriggeringState.DRAGGING
            )

            autoplayMode = PlayableItemsContainer.AutoplayMode.ONE_AT_A_TIME
            adapter = BasicVideoItemsRecyclerViewAdapter(
                context = context!!,
                items = VideoProvider.getVideos(count = 100, mute = true).toMutableList(),
                arviConfig = Config.Builder()
                    .cache(ExoPlayerUtils.getCache(context!!))
                    .build()
            )
        }
    }
    
    //...

    override fun onResume() {
        super.onResume()

        recyclerView.onResume()
    }

    override fun onPause() {
        super.onPause()

        recyclerView.onPause()
    }

    override fun onDestroy() {
    	recyclerView?.onDestroy()
	
        super.onDestroy()
    }
    
    //...

}


Java (click to expand)

public final class BasicVideosFragment extends BaseFragment {

    //...

    @Override
    public void init(Bundle savedInstanceState) {
        mRecyclerView.setPlaybackTriggeringStates(
            PlayableItemsContainer.PlaybackTriggeringState.IDLING,
            PlayableItemsContainer.PlaybackTriggeringState.DRAGGING
        );
        mRecyclerView.setAutoplayMode(PlayableItemsContainer.AutoplayMode.ONE_AT_A_TIME);
        mRecyclerView.setAdapter(new BasicVideoItemsRecyclerViewAdapter(
            context,
            VideoProvider.getVideos(100, true),
            new Config.Builder()
                .cache(ExoPlayerUtils.getCache(context))
                .build()
        ));
    }
    
    //...

    @Override
    public void onResume() {
        super.onResume();

        mRecyclerView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();

        mRecyclerView.onPause();
    }

    @Override
    public void onDestroy() {
        mRecyclerView.onDestroy();
    
        super.onDestroy();
    }
    
    //...

}


For more advanced use cases

See: Advanced Use Cases

Adapster-based Implementation

Adapster-based implementation requires both the official Adapster and arvi-adapster module dependencies.

Latest ARVI version: Download

implementation "com.arthurivanets.arvi:arvi-adapster:X.Y.Z"

While the implementation itself shares most of the steps with the Basic Implementation, one of the things that should be taken into account is the fact that the implementation of your Item ViewHolder should be based on the AdapsterPlayableItemViewHolder instead of the PlayableItemViewHolder.

Kotlin (click to expand)

//...
import com.arthurivanets.arvi.adapster.AdapsterPlayableItemViewHolder

class VideoItemViewHolder(
    parent : ViewGroup,
    itemView : View,
    private val resources : VideoItemResources?
) : AdapsterPlayableItemViewHolder<Video>(parent, itemView) {

    //...

    override fun getUrl() : String {
        return "video_url..."
    }
    
    //...

}


Java (click to expand)

//...
import com.arthurivanets.arvi.adapster.AdapsterPlayableItemViewHolder;

public final class VideoItemViewHolder extends AdapsterPlayableItemViewHolder<Video> {

    //...
    
    @Override
    public final String getUrl() {
        return "video_url...";
    }
    
    //...

}


See: VideoItemsRecyclerViewAdapter, AdapsterVideosFragment, AdapsterPlayableItemViewHolder and VideoItemViewHolder

For more advanced use cases

See: Advanced Use Cases

Advanced Use Cases

Sometimes you require something more than a basic implementation, whether it's an ability to enable the caching of your videos or a way to authorize your HTTP Video requests, you name it; for that reason a list of the most common advanced use cases has been compiled.

Most common advanced use cases include, but not limited to:

1. Video Caching

In order to enable the video caching you should provide an instance of the ExoPlayer Cache via the ARVI Config to your Item ViewHolders, and then use the provided Config within each corresponding Item ViewHolder.

Kotlin (click to expand)

//...
import com.arthurivanets.arvi.Config

class BasicVideoItemViewHolder(
    parent : ViewGroup,
    itemView : View,
    val arviConfig : Config
) : PlayableItemViewHolder(parent, itemView) {

    //...

    override fun getUrl() : String {
        return "video_url..."
    }
    
    override fun getConfig() : Config {
        return arviConfig
    }
    
    //...

}

Adapster-based

//...
import com.arthurivanets.arvi.adapster.AdapsterPlayableItemViewHolder

class VideoItemViewHolder(
    parent : ViewGroup,
    itemView : View,
    private val resources : VideoItemResources?
) : AdapsterPlayableItemViewHolder<Video>(parent, itemView) {

    //...

    override fun getUrl() : String {
        return "video_url..."
    }
    
    override fun getConfig() : Config {
        return (resources?.arviConfig ?: super.getConfig())
    }
    
    //...

}


Java (click to expand)

//...
import com.arthurivanets.arvi.Config;

public final class BasicVideoItemViewHolder extends PlayableItemViewHolder {

    //...

    @Override
    public final String getUrl() {
        return "video_url...";
    }
    
    @Override
    public final Config getConfig() {
        return arviConfig;
    }
    
    //...

}

Adapster-based

//...
import com.arthurivanets.arvi.adapster.AdapsterPlayableItemViewHolder;

public final class VideoItemViewHolder extends AdapsterPlayableItemViewHolder<Video> {

    //...

    @Override
    public final String getUrl() {
        return "video_url...";
    }
    
    @Override
    public final Config getConfig() {
        return resources.arviConfig;
    }
    
    //...

}


The general ExoPlayer Cache instance can be easily created using the utility extension methods found in the ArviExtensions of the arvi-ktx module, or you can resort to your own ExoPlayer Cache instance creation approach; choose the approach that fits your requirements the best.

For more details

See: BasicVideosFragment, BasicVideoItemsRecyclerViewAdapter, BasicVideoItemViewHolder, AdapsterVideosFragment, VideoItemsRecyclerViewAdapter, VideoItem, VideoItemViewHolder, VideoItemResources, Config, ArviExtensions, Cache

2. HTTP Video Request Authorization

In cases when your video requests require authorization, you can use the RequestAuthorizer to provide the necessary auth token whenever the player requests it. The created RequestAuthorizer should be associated with the ArviHttpDataSourceFactory and passed around in the ARVI Config object.

RequestAuthorizer

Kotlin (click to expand)

//...
import com.arthurivanets.arvi.player.datasource.RequestAuthorizer

class ArviRequestAuthorizer(private val authTokenProvider : AuthTokenProvider) : RequestAuthorizer {

    override fun getAuthorization() : String {
        return "Bearer ${authTokenProvider.getAuthToken()}"
    }

}


Java (click to expand)

//...
import com.arthurivanets.arvi.player.datasource.RequestAuthorizer;

public final class ArviRequestAuthorizer extends RequestAuthorizer {

    //...

    @Override
    public final String getAuthorization() {
        return ("Bearer " + authTokenProvider.getAuthToken());
    }

}


ARVI Config

Kotlin (click to expand)

//...
val config = Config.Builder()
    .dataSourceFactory(
        ArviHttpDataSourceFactory(context.playerProvider.libraryName).apply {
            setConnectTimeout(REQUEST_CONNECT_TIMEOUT_IN_MILLIS)
            setReadTimeout(REQUEST_READ_TIMEOUT_IN_MILLIS)
            
            // Your request authorizer
            setRequestAuthorizer(ArviRequestAuthorizer(...))
        }
    )
    .build()


Java (click to expand)

//...
final ArviHttpDataSourceFactory dataSourceFactory = new ArviHttpDataSourceFactory(PlayerProviderImpl.getInstance(context).getLibraryName());
dataSourceFactory.setConnectTimeout(REQUEST_CONNECT_TIMEOUT_IN_MILLIS);
dataSourceFactory.setReadTimeout(REQUEST_READ_TIMEOUT_IN_MILLIS);

// Your request authorizer
dataSourceFactory.setRequestAuthorizer(new ArviRequestAuthorizer(...));

// the final Config
final Config config = new Config.Builder()
    .dataSourceFactory(dataSourceFactory)
    .build();


See: RequestAuthorizer, ArviHttpDataSourceFactory, Config

3. ViewHolder Playback Control

All Playable Item ViewHolders are capable of controlling almost every aspect of the corresponding playback, thus giving you more power in terms of the actual implementation.

For more details on what possibilities the Playable gives you

See: Playable

4. ARVI Players

All the Players created using the PlayerProviderImpl can be used as stand alone players, as they are totally independent entities, the only thing to remember here is that you should properly handle the player binding/unbinding events to avoid the potential memory leaks and other related issues.

See: Player, PlayerProvider, PlayerProviderImpl

GitHub