Added fast scrollers to RecyclerViews, and also integrated an

OkHttpDataStream for ExoPlayer so we can take advantage of its caching
capabilities.
This commit is contained in:
casey langen 2017-04-30 14:23:23 -07:00
parent 881ef906a2
commit 8ce038c416
9 changed files with 120 additions and 32 deletions

View File

@ -29,6 +29,7 @@ android {
repositories {
flatDir { dirs 'libs' }
maven { url "https://jitpack.io" }
}
dependencies {
@ -47,10 +48,12 @@ dependencies {
compile 'io.reactivex.rxjava2:rxjava:2.0.9'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.google.android.exoplayer:exoplayer:r2.4.0'
compile 'com.google.android.exoplayer:extension-okhttp:r2.4.0'
compile 'com.github.pluscubed:recycler-fast-scroll:0.3.2@aar'
compile 'com.android.support:appcompat-v7:25.1.1'
compile 'com.android.support:recyclerview-v7:25.1.1'
compile 'com.android.support:design:25.1.1'
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
testCompile 'junit:junit:4.12'
}

View File

@ -9,6 +9,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
@ -19,16 +20,20 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import io.casey.musikcube.remote.Application;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
public class ExoPlayerWrapper extends PlayerWrapper {
private BandwidthMeter bandwidth;
private static OkHttpClient audioStreamClient = null;
private DefaultBandwidthMeter bandwidth;
private DataSource.Factory datasources;
private ExtractorsFactory extractors;
private MediaSource source;
@ -41,13 +46,38 @@ public class ExoPlayerWrapper extends PlayerWrapper {
final TrackSelection.Factory trackFactory = new AdaptiveTrackSelection.Factory(bandwidth);
final TrackSelector trackSelector = new DefaultTrackSelector(trackFactory);
this.player = ExoPlayerFactory.newSimpleInstance(c, trackSelector);
this.datasources = new DefaultDataSourceFactory(c, Util.getUserAgent(c, "musikdroid"));
this.extractors = new DefaultExtractorsFactory();
this.player.addListener(eventListener);
}
private void initDataSourceFactory(final String uri) {
final Context context = Application.getInstance();
synchronized (ExoPlayerWrapper.class) {
if (audioStreamClient == null) {
final File path = new File(context.getExternalCacheDir(), "audio");
audioStreamClient = new OkHttpClient.Builder()
.cache(new Cache(path, 1048576 * 256)) /* 256 meg cache */
.build();
}
}
if (uri.startsWith("http")) {
this.datasources = new OkHttpDataSourceFactory(
audioStreamClient,
Util.getUserAgent(context, "musikdroid"),
bandwidth);
}
else {
this.datasources = new DefaultDataSourceFactory(
context, Util.getUserAgent(context, "musikdroid"));
}
}
@Override
public void play(String uri) {
initDataSourceFactory(uri);
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
this.player.setPlayWhenReady(true);
this.player.prepare(this.source);
@ -57,6 +87,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
@Override
public void prefetch(String uri) {
initDataSourceFactory(uri);
this.prefetch = true;
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
this.player.setPlayWhenReady(false);

View File

@ -11,6 +11,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.json.JSONArray;
import org.json.JSONObject;
@ -89,9 +91,9 @@ public class AlbumBrowseActivity extends WebSocketActivityBase implements Filter
this.wss = getWebSocketService();
this.adapter = new Adapter();
final RecyclerFastScroller fastScroller = (RecyclerFastScroller) findViewById(R.id.fast_scroller);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
Views.setupDefaultRecyclerView(this, recyclerView, adapter);
Views.setupDefaultRecyclerView(this, recyclerView, fastScroller, adapter);
transport = Views.addTransportFragment(this,
(TransportFragment fragment) -> adapter.notifyDataSetChanged());

View File

@ -11,6 +11,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.json.JSONArray;
import org.json.JSONObject;
@ -88,9 +90,9 @@ public class CategoryBrowseActivity extends WebSocketActivityBase implements Fil
setTitle(CATEGORY_NAME_TO_TITLE.get(category));
}
final RecyclerFastScroller fastScroller = (RecyclerFastScroller) findViewById(R.id.fast_scroller);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
Views.setupDefaultRecyclerView(this, recyclerView, this.adapter);
Views.setupDefaultRecyclerView(this, recyclerView, fastScroller, adapter);
transport = Views.addTransportFragment(this,
(TransportFragment fragment) -> adapter.notifyDataSetChanged());

View File

@ -10,6 +10,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.json.JSONObject;
import io.casey.musikcube.remote.R;
@ -47,12 +49,14 @@ public class PlayQueueActivity extends WebSocketActivityBase {
Views.enableUpNavigation(this);
this.adapter = new Adapter();
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
Views.setupDefaultRecyclerView(this, recyclerView, adapter);
final RecyclerFastScroller fastScroller = (RecyclerFastScroller) findViewById(R.id.fast_scroller);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
Views.setupDefaultRecyclerView(this, recyclerView, fastScroller, adapter);
this.tracks = new TrackListSlidingWindow<>(
recyclerView,
fastScroller,
this.wss,
this.playback.getPlaylistQueryFactory(),
(JSONObject obj) -> obj);

View File

@ -11,6 +11,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.json.JSONObject;
import io.casey.musikcube.remote.R;
@ -79,11 +81,16 @@ public class TrackListActivity extends WebSocketActivityBase implements Filterab
createCategoryQueryFactory(categoryType, categoryId);
final Adapter adapter = new Adapter();
final RecyclerFastScroller fastScroller = (RecyclerFastScroller) findViewById(R.id.fast_scroller);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
Views.setupDefaultRecyclerView(this, recyclerView, adapter);
Views.setupDefaultRecyclerView(this, recyclerView, fastScroller, adapter);
tracks = new TrackListSlidingWindow<>(
recyclerView, getWebSocketService(), queryFactory, (JSONObject track) -> track);
recyclerView,
fastScroller,
getWebSocketService(),
queryFactory,
(JSONObject track) -> track);
transport = Views.addTransportFragment(this,
(TransportFragment fragment) -> adapter.notifyDataSetChanged());

View File

@ -1,6 +1,10 @@
package io.casey.musikcube.remote.ui.model;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.View;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.json.JSONArray;
import org.json.JSONObject;
@ -18,10 +22,12 @@ public class TrackListSlidingWindow<TrackType> {
private int count = 0;
private RecyclerView recyclerView;
private RecyclerFastScroller fastScroller;
private WebSocketService wss;
private Mapper<TrackType> mapper;
private QueryFactory queryFactory;
private int scrollState = RecyclerView.SCROLL_STATE_IDLE;
private boolean fastScrollActive = false;
private int queryOffset = -1, queryLimit = -1;
private int initialPosition = -1;
private int windowSize = DEFAULT_WINDOW_SIZE;
@ -54,28 +60,30 @@ public class TrackListSlidingWindow<TrackType> {
}
public TrackListSlidingWindow(RecyclerView recyclerView,
RecyclerFastScroller fastScroller,
WebSocketService wss,
QueryFactory queryFactory,
Mapper<TrackType> mapper) {
this.recyclerView = recyclerView;
this.fastScroller = fastScroller;
this.wss = wss;
this.queryFactory = queryFactory;
this.mapper = mapper;
}
public TrackListSlidingWindow(WebSocketService wss,
QueryFactory queryFactory,
Mapper<TrackType> mapper) {
this.recyclerView = null;
this.wss = wss;
this.queryFactory = queryFactory;
this.mapper = mapper;
}
public void setQueryFactory(final QueryFactory factory) {
this.queryFactory = factory;
requery();
}
private View.OnTouchListener fastScrollerTouch = (view, event) -> {
if (event != null) {
final int type = event.getActionMasked();
if (type == MotionEvent.ACTION_DOWN) {
fastScrollActive = true;
}
else if (type == MotionEvent.ACTION_UP) {
fastScrollActive = false;
requery();
}
}
return false;
};
public void requery() {
if (connected) {
@ -118,6 +126,10 @@ public class TrackListSlidingWindow<TrackType> {
if (this.recyclerView != null) {
this.recyclerView.removeOnScrollListener(scrollListener);
}
if (this.fastScroller != null) {
fastScroller.setOnHandleTouchListener(null);
}
}
public void resume() {
@ -125,8 +137,13 @@ public class TrackListSlidingWindow<TrackType> {
this.recyclerView.addOnScrollListener(scrollListener);
}
if (this.fastScroller != null) {
fastScroller.setOnHandleTouchListener(fastScrollerTouch);
}
this.wss.addClient(this.client);
connected = true;
fastScrollActive = false;
}
public void setInitialPosition(int initialIndex) {
@ -177,7 +194,7 @@ public class TrackListSlidingWindow<TrackType> {
}
private void getPageAround(int index) {
if (!connected) {
if (!connected || scrolling()) {
return;
}
@ -234,11 +251,15 @@ public class TrackListSlidingWindow<TrackType> {
}
}
private boolean scrolling() {
return scrollState != RecyclerView.SCROLL_STATE_IDLE || fastScrollActive;
}
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
scrollState = newState;
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!scrolling()) {
notifyAdapterChanged();
}
}

View File

@ -17,6 +17,8 @@ import android.view.ViewPropertyAnimator;
import android.widget.CheckBox;
import android.widget.EditText;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import io.casey.musikcube.remote.R;
import io.casey.musikcube.remote.ui.activity.Filterable;
import io.casey.musikcube.remote.ui.fragment.TransportFragment;
@ -35,11 +37,13 @@ public final class Views {
public static void setupDefaultRecyclerView(final Context context,
final RecyclerView recyclerView,
final RecyclerFastScroller fastScroller,
final RecyclerView.Adapter adapter) {
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.setAdapter(adapter);
fastScroller.attachRecyclerView(recyclerView);
final DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(context, layoutManager.getOrientation());

View File

@ -1,15 +1,29 @@
<?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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
android:layout_height="0dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.pluscubed.recyclerfastscroll.RecyclerFastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:layout_alignParentRight="true" />
</RelativeLayout>
<FrameLayout
android:id="@+id/transport_container"