ExoPlayer-based Android library that makes the implementation of the autoplayable RecyclerView video items an easy task
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.
- Demo
- Getting Started
- Basic Implementation
- Adapster-based Implementation
- Advanced Use Cases
- Contribution
- Hall of Fame
- License
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 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
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.
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
andfragment_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 requires both the official Adapster and arvi-adapster
module dependencies.
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
andVideoItemViewHolder
For more advanced use cases
See: 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();
3. ViewHolder Playback Control
All Playable
Item ViewHolder
s 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 Player
s 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 the CONTRIBUTING.md file.
Owly |
Using ARVI in your app and want it to get listed here? Email me at arthur.ivanets.work@gmail.com!
ARVI is licensed under the Apache 2.0 License.