Show live comments in RPAN broadcasts.

This commit is contained in:
Alex Ning 2021-07-06 20:38:58 +08:00
parent 4204ce4773
commit 372f373601
9 changed files with 358 additions and 6 deletions

View File

@ -177,6 +177,8 @@ dependencies {
implementation 'androidx.palette:palette:1.0.0'
implementation 'com.tinder.scarlet:scarlet:0.1.12'
/**** Builds and flavors ****/
// debugImplementation because LeakCanary should only run in debug builds.

View File

@ -0,0 +1,15 @@
package ml.docilealligator.infinityforreddit;
public class RPANComment {
public String author;
public String authorIconImage;
public String content;
public long createdUTC;
public RPANComment(String author, String authorIconImage, String content, long createdUTC) {
this.author = author;
this.authorIconImage = authorIconImage;
this.content = content;
this.createdUTC = createdUTC;
}
}

View File

@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
@ -28,6 +29,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@ -217,7 +219,25 @@ public class RPANActivity extends AppCompatActivity {
sectionsPagerAdapter = new SectionsPagerAdapter(this);
viewPager2.setAdapter(sectionsPagerAdapter);
viewPager2.setOffscreenPageLimit(3);
//fixViewPager2Sensitivity(viewPager2);
fixViewPager2Sensitivity(viewPager2);
}
private void fixViewPager2Sensitivity(ViewPager2 viewPager2) {
try {
Field recyclerViewField = ViewPager2.class.getDeclaredField("mRecyclerView");
recyclerViewField.setAccessible(true);
RecyclerView recyclerView = (RecyclerView) recyclerViewField.get(viewPager2);
Field touchSlopField = RecyclerView.class.getDeclaredField("mTouchSlop");
touchSlopField.setAccessible(true);
Object touchSlopBox = touchSlopField.get(recyclerView);
if (touchSlopBox != null) {
int touchSlop = (int) touchSlopBox;
touchSlopField.set(recyclerView, touchSlop * 4);
}
} catch (NoSuchFieldException | IllegalAccessException ignore) {}
}
private class SectionsPagerAdapter extends FragmentStateAdapter {

View File

@ -0,0 +1,83 @@
package ml.docilealligator.infinityforreddit.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RPANComment;
public class RPANCommentStreamRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RequestManager glide;
private ArrayList<RPANComment> rpanComments;
public RPANCommentStreamRecyclerViewAdapter(Context context) {
glide = Glide.with(context);
rpanComments = new ArrayList<>();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RPANCommentViewHolder(LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_rpan_comment, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof RPANCommentViewHolder) {
((RPANCommentViewHolder) holder).authorTextView.setText(rpanComments.get(position).author);
((RPANCommentViewHolder) holder).contentTextView.setText(rpanComments.get(position).content);
glide.load(rpanComments.get(position).authorIconImage)
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))
.error(glide.load(R.drawable.subreddit_default_icon)
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))))
.into(((RPANCommentViewHolder) holder).iconImageView);
}
}
@Override
public int getItemCount() {
return rpanComments == null ? 0 : rpanComments.size();
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
if (holder instanceof RPANCommentViewHolder) {
glide.clear(((RPANCommentViewHolder) holder).iconImageView);
}
}
public void addRPANComment(RPANComment rpanComment) {
rpanComments.add(rpanComment);
notifyItemInserted(rpanComments.size() - 1);
}
class RPANCommentViewHolder extends RecyclerView.ViewHolder {
ImageView iconImageView;
TextView authorTextView;
TextView timeTextView;
TextView contentTextView;
public RPANCommentViewHolder(@NonNull View itemView) {
super(itemView);
iconImageView = itemView.findViewById(R.id.icon_image_view_item_rpan_comment);
authorTextView = itemView.findViewById(R.id.author_text_view_item_rpan_comment);
contentTextView = itemView.findViewById(R.id.content_text_view_item_rpan_comment);
}
}
}

View File

@ -2,8 +2,11 @@ package ml.docilealligator.infinityforreddit.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -11,7 +14,9 @@ import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
@ -28,9 +33,14 @@ import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
@ -39,19 +49,30 @@ import butterknife.ButterKnife;
import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RPANBroadcast;
import ml.docilealligator.infinityforreddit.RPANComment;
import ml.docilealligator.infinityforreddit.adapters.RPANCommentStreamRecyclerViewAdapter;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.utils.JSONUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
public class ViewRPANBroadcastFragment extends Fragment {
public static final String EXTRA_RPAN_BROADCAST = "ERB";
private static final String IS_MUTE_STATE = "IMS";
@BindView(R.id.constraint_layout_exo_rpan_broadcast_playback_control_view)
ConstraintLayout constraintLayout;
@BindView(R.id.player_view_view_rpan_broadcast_fragment)
PlayerView playerView;
@BindView(R.id.mute_exo_playback_control_view)
@BindView(R.id.recycler_view_exo_rpan_broadcast_playback_control_view)
RecyclerView recyclerView;
@BindView(R.id.mute_exo_rpan_broadcast_playback_control_view)
ImageButton muteButton;
@BindView(R.id.hd_exo_playback_control_view)
@BindView(R.id.hd_exo_rpan_broadcast_playback_control_view)
ImageButton hdButton;
@Inject
@Named("default")
@ -62,12 +83,14 @@ public class ViewRPANBroadcastFragment extends Fragment {
@Inject
CustomThemeWrapper mCustomThemeWrapper;
@Inject
SimpleCache mSimpleCache;
Executor mExecutor;
private AppCompatActivity mActivity;
private RPANBroadcast rpanBroadcast;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
private DataSource.Factory dataSourceFactory;
private Handler handler;
private RPANCommentStreamRecyclerViewAdapter adapter;
private boolean wasPlaying;
private boolean isMute = false;
@ -90,6 +113,20 @@ public class ViewRPANBroadcastFragment extends Fragment {
rpanBroadcast = getArguments().getParcelable(EXTRA_RPAN_BROADCAST);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT || getResources().getBoolean(R.bool.isTablet)) {
//Set player controller bottom margin in order to display it above the navbar
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
//LinearLayout controllerLinearLayout = findViewById(R.id.linear_layout_exo_playback_control_view);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) constraintLayout.getLayoutParams();
params.bottomMargin = getResources().getDimensionPixelSize(resourceId);
} else {
//Set player controller right margin in order to display it above the navbar
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
//LinearLayout controllerLinearLayout = findViewById(R.id.linear_layout_exo_playback_control_view);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) constraintLayout.getLayoutParams();
params.rightMargin = getResources().getDimensionPixelSize(resourceId);
}
playerView.setControllerVisibilityListener(visibility -> {
switch (visibility) {
case View.GONE:
@ -186,6 +223,21 @@ public class ViewRPANBroadcastFragment extends Fragment {
}
});
adapter = new RPANCommentStreamRecyclerViewAdapter(mActivity);
recyclerView.setAdapter(adapter);
handler = new Handler();
Request request = new Request.Builder().url(rpanBroadcast.rpanPost.liveCommentsWebsocketUrl).build();
CommentStreamWebSocketListener listener = new CommentStreamWebSocketListener(this::parseComment);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
WebSocket webSocket = okHttpClient.newWebSocket(request, listener);
okHttpClient.dispatcher().executorService().shutdown();
return rootView;
}
@ -203,6 +255,26 @@ public class ViewRPANBroadcastFragment extends Fragment {
return false;
}
private void parseComment(String commentJson) {
mExecutor.execute(() -> {
try {
JSONObject commentObject = new JSONObject(commentJson);
if (commentObject.getString(JSONUtils.TYPE_KEY).equals("new_comment")) {
JSONObject payload = commentObject.getJSONObject(JSONUtils.PAYLOAD_KEY);
RPANComment rpanComment = new RPANComment(
payload.getString(JSONUtils.AUTHOR_KEY),
payload.getString(JSONUtils.AUTHOR_ICON_IMAGE),
payload.getString(JSONUtils.BODY_KEY),
payload.getLong(JSONUtils.CREATED_UTC_KEY));
handler.post(() -> adapter.addRPANComment(rpanComment));
}
} catch (JSONException e) {
e.printStackTrace();
}
});
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
@ -237,4 +309,22 @@ public class ViewRPANBroadcastFragment extends Fragment {
super.onAttach(context);
mActivity = (AppCompatActivity) context;
}
private static class CommentStreamWebSocketListener extends WebSocketListener {
MessageReceivedListener messageReceivedListener;
CommentStreamWebSocketListener(MessageReceivedListener messageReceivedListener) {
this.messageReceivedListener = messageReceivedListener;
}
@Override
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
Log.i("asfasdf", "s " + text);
messageReceivedListener.onMessage(text);
}
interface MessageReceivedListener {
void onMessage(String text);
}
}
}

View File

@ -162,4 +162,6 @@ public class JSONUtils {
public static final String CHAT_DISABLED_KEY = "chat_disabled";
public static final String BROADCAST_TIME_KEY = "broadcast_time";
public static final String ESTIMATED_REMAINING_TIME_KEY = "estimated_remaining_time";
public static final String PAYLOAD_KEY = "payload";
public static final String AUTHOR_ICON_IMAGE = "author_icon_img";
}

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#44000000"
android:id="@+id/constraint_layout_exo_rpan_broadcast_playback_control_view"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/control_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@+id/hd_exo_rpan_broadcast_playback_control_view"
android:src="@drawable/ic_video_quality_24dp"
style="@style/ExoMediaButton"
android:visibility="gone" />
<ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind" />
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause" />
<ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward" />
<ImageButton
android:id="@+id/mute_exo_rpan_broadcast_playback_control_view"
style="@style/ExoMediaButton"
android:visibility="gone" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_exo_rpan_broadcast_playback_control_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingTop="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/control_linear_layout"
app:layout_constraintBottom_toTopOf="@id/time_bar_linear_layout"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<LinearLayout
android:id="@+id/time_bar_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:padding="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingStart="0dp"
android:paddingEnd="4dp"
android:includeFontPadding="false"
android:textColor="#FFFFFF"
android:fontFamily="?attr/font_family" />
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="26dp"
app:bar_height="2dp"
app:scrubber_color="@color/colorAccent"
app:played_color="@color/colorAccent" />
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:includeFontPadding="false"
android:textColor="#FFFFFF"
android:fontFamily="?attr/font_family" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,6 +10,6 @@
android:id="@+id/player_view_view_rpan_broadcast_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_playback_control_view" />
app:controller_layout_id="@layout/exo_rpan_broadcast_playback_control_view" />
</FrameLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/icon_image_view_item_rpan_comment"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="0dp"
android:orientation="vertical">
<TextView
android:id="@+id/author_text_view_item_rpan_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary" />
<TextView
android:id="@+id/content_text_view_item_rpan_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="#FFFFFF" />
</LinearLayout>
</LinearLayout>