From bd0068eb6c3bd14b86dc44788291c7cf9471af1b Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Mon, 5 Jul 2021 22:38:28 +0800 Subject: [PATCH] Start adding RPAN support. --- app/src/main/AndroidManifest.xml | 7 +- .../infinityforreddit/AppComponent.java | 6 + .../infinityforreddit/AppModule.java | 11 + .../infinityforreddit/RPANBroadcast.java | 226 +++++++++++++++ .../activities/MainActivity.java | 14 +- .../activities/RPANActivity.java | 258 ++++++++++++++++++ .../infinityforreddit/apis/Strapi.java | 12 + .../fragments/ViewRPANBroadcastFragment.java | 225 +++++++++++++++ .../infinityforreddit/utils/APIUtils.java | 1 + .../infinityforreddit/utils/JSONUtils.java | 27 ++ .../main/res/layout/activity_rpanactivity.xml | 33 +++ .../layout/fragment_view_rpan_broadcast.xml | 22 ++ app/src/main/res/values/strings.xml | 4 + 13 files changed, 835 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/RPANBroadcast.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/apis/Strapi.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java create mode 100644 app/src/main/res/layout/activity_rpanactivity.xml create mode 100644 app/src/main/res/layout/fragment_view_rpan_broadcast.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a4275813..c492d535 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,7 +32,12 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true" tools:replace="android:label"> - + CREATOR = new Creator() { + @Override + public RPANBroadcast createFromParcel(Parcel in) { + return new RPANBroadcast(in); + } + + @Override + public RPANBroadcast[] newArray(int size) { + return new RPANBroadcast[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeInt(upvotes); + parcel.writeInt(downvotes); + parcel.writeInt(uniqueWatchers); + parcel.writeInt(continuousWatchers); + parcel.writeInt(totalContinuousWatchers); + parcel.writeByte((byte) (chatDisabled ? 1 : 0)); + parcel.writeDouble(broadcastTime); + parcel.writeDouble(estimatedRemainingTime); + parcel.writeParcelable(rpanPost, i); + parcel.writeParcelable(rpanStream, i); + } + + public static class RPANPost implements Parcelable{ + public String title; + public String subredditName; + public String subredditIconUrl; + public String username; + public int postScore; + public String voteState; + public double upvoteRatio; + public String postPermalink; + public boolean isNsfw; + public boolean isLocked; + public boolean isArchived; + public boolean isSpoiler; + public String suggestedCommentSort; + public String liveCommentsWebsocketUrl; + + public RPANPost(String title, String subredditName, String subredditIconUrl, String username, + int postScore, String voteState, double upvoteRatio, String postPermalink, String rpanUrl, + boolean isNsfw, boolean isLocked, boolean isArchived, boolean isSpoiler, + String suggestedCommentSort, String liveCommentsWebsocketUrl) { + this.title = title; + this.subredditName = subredditName; + this.subredditIconUrl = subredditIconUrl; + this.username = username; + this.postScore = postScore; + this.voteState = voteState; + this.upvoteRatio = upvoteRatio; + this.postPermalink = postPermalink; + this.isNsfw = isNsfw; + this.isLocked = isLocked; + this.isArchived = isArchived; + this.isSpoiler = isSpoiler; + this.suggestedCommentSort = suggestedCommentSort; + this.liveCommentsWebsocketUrl = liveCommentsWebsocketUrl; + } + + protected RPANPost(Parcel in) { + title = in.readString(); + subredditName = in.readString(); + subredditIconUrl = in.readString(); + username = in.readString(); + postScore = in.readInt(); + voteState = in.readString(); + upvoteRatio = in.readDouble(); + postPermalink = in.readString(); + isNsfw = in.readByte() != 0; + isLocked = in.readByte() != 0; + isArchived = in.readByte() != 0; + isSpoiler = in.readByte() != 0; + suggestedCommentSort = in.readString(); + liveCommentsWebsocketUrl = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public RPANPost createFromParcel(Parcel in) { + return new RPANPost(in); + } + + @Override + public RPANPost[] newArray(int size) { + return new RPANPost[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(title); + parcel.writeString(subredditName); + parcel.writeString(subredditIconUrl); + parcel.writeString(username); + parcel.writeInt(postScore); + parcel.writeString(voteState); + parcel.writeDouble(upvoteRatio); + parcel.writeString(postPermalink); + parcel.writeByte((byte) (isNsfw ? 1 : 0)); + parcel.writeByte((byte) (isLocked ? 1 : 0)); + parcel.writeByte((byte) (isArchived ? 1 : 0)); + parcel.writeByte((byte) (isSpoiler ? 1 : 0)); + parcel.writeString(suggestedCommentSort); + parcel.writeString(liveCommentsWebsocketUrl); + } + } + + public static class RPANStream implements Parcelable { + public String streamId; + public String hlsUrl; + public String thumbnail; + public int width; + public int height; + public long publishAt; + public String state; + + public RPANStream(String streamId, String hlsUrl, String thumbnail, int width, int height, long publishAt, + String state) { + this.streamId = streamId; + this.hlsUrl = hlsUrl; + this.thumbnail = thumbnail; + this.width = width; + this.height = height; + this.publishAt = publishAt; + this.state = state; + } + + protected RPANStream(Parcel in) { + streamId = in.readString(); + hlsUrl = in.readString(); + thumbnail = in.readString(); + width = in.readInt(); + height = in.readInt(); + publishAt = in.readLong(); + state = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public RPANStream createFromParcel(Parcel in) { + return new RPANStream(in); + } + + @Override + public RPANStream[] newArray(int size) { + return new RPANStream[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(streamId); + parcel.writeString(hlsUrl); + parcel.writeString(thumbnail); + parcel.writeInt(width); + parcel.writeInt(height); + parcel.writeLong(publishAt); + parcel.writeString(state); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java index 6549c44e..4a2c945a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java @@ -9,8 +9,6 @@ import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; @@ -83,8 +81,6 @@ import ml.docilealligator.infinityforreddit.SortType; import ml.docilealligator.infinityforreddit.SortTypeSelectionCallback; import ml.docilealligator.infinityforreddit.account.AccountViewModel; import ml.docilealligator.infinityforreddit.adapters.NavigationDrawerRecyclerViewAdapter; -import ml.docilealligator.infinityforreddit.adapters.SubredditAutocompleteRecyclerViewAdapter; -import ml.docilealligator.infinityforreddit.apis.RedditAPI; import ml.docilealligator.infinityforreddit.asynctasks.InsertSubscribedThings; import ml.docilealligator.infinityforreddit.asynctasks.SwitchAccount; import ml.docilealligator.infinityforreddit.asynctasks.SwitchToAnonymousMode; @@ -110,20 +106,16 @@ import ml.docilealligator.infinityforreddit.multireddit.MultiRedditViewModel; import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.post.PostDataSource; import ml.docilealligator.infinityforreddit.readpost.InsertReadPost; -import ml.docilealligator.infinityforreddit.subreddit.ParseSubredditData; import ml.docilealligator.infinityforreddit.subreddit.SubredditData; import ml.docilealligator.infinityforreddit.subscribedsubreddit.SubscribedSubredditData; import ml.docilealligator.infinityforreddit.subscribedsubreddit.SubscribedSubredditViewModel; import ml.docilealligator.infinityforreddit.subscribeduser.SubscribedUserData; import ml.docilealligator.infinityforreddit.user.FetchUserData; import ml.docilealligator.infinityforreddit.user.UserData; -import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.CustomThemeSharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import retrofit2.Retrofit; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO; @@ -1381,7 +1373,7 @@ public class MainActivity extends BaseActivity implements SortTypeSelectionCallb } private void goToSubreddit() { - View rootView = getLayoutInflater().inflate(R.layout.dialog_go_to_thing_edit_text, coordinatorLayout, false); + /*View rootView = getLayoutInflater().inflate(R.layout.dialog_go_to_thing_edit_text, coordinatorLayout, false); TextInputEditText thingEditText = rootView.findViewById(R.id.text_input_edit_text_go_to_thing_edit_text); RecyclerView recyclerView = rootView.findViewById(R.id.recycler_view_go_to_thing_edit_text); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -1475,7 +1467,9 @@ public class MainActivity extends BaseActivity implements SortTypeSelectionCallb imm.hideSoftInputFromWindow(thingEditText.getWindowToken(), 0); } }) - .show(); + .show();*/ + Intent intent = new Intent(this, RPANActivity.class); + startActivity(intent); } private void goToUser() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java new file mode 100644 index 00000000..22fa13db --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java @@ -0,0 +1,258 @@ +package ml.docilealligator.infinityforreddit.activities; + +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.appbar.AppBarLayout; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.RPANBroadcast; +import ml.docilealligator.infinityforreddit.apis.Strapi; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.fragments.ViewRPANBroadcastFragment; +import ml.docilealligator.infinityforreddit.utils.APIUtils; +import ml.docilealligator.infinityforreddit.utils.JSONUtils; +import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class RPANActivity extends BaseActivity { + + @BindView(R.id.coordinator_layout_rpan_activity) + CoordinatorLayout coordinatorLayout; + @BindView(R.id.appbar_layout_rpan_activity) + AppBarLayout appBarLayout; + @BindView(R.id.toolbar_rpan_activity) + Toolbar toolbar; + @BindView(R.id.view_pager_2_rpan_activity) + ViewPager2 viewPager2; + @Inject + @Named("strapi") + Retrofit strapiRetrofit; + @Inject + @Named("default") + SharedPreferences mSharedPreferences; + @Inject + @Named("current_account") + SharedPreferences mCurrentAccountSharedPreferences; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + @Inject + Executor mExecutor; + private String mAccessToken; + private String mAccountName; + private ArrayList rpanBroadcasts; + private String nextCursor; + private SectionsPagerAdapter sectionsPagerAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + ((Infinity) getApplication()).getAppComponent().inject(this); + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_rpanactivity); + + ButterKnife.bind(this); + + applyCustomTheme(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + + if (isChangeStatusBarIconColor()) { + addOnOffsetChangedListener(appBarLayout); + } + + if (isImmersiveInterface()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } else { + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + adjustToolbar(toolbar); + } + } + + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); + mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_NAME, null); + + loadRPANVideos(); + } + + private void loadRPANVideos() { + strapiRetrofit.create(Strapi.class).getAllBroadcasts(APIUtils.getOAuthHeader(mAccessToken)).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + parseRPANBroadcasts(response.body()); + } else { + Toast.makeText(RPANActivity.this, + R.string.load_rpan_broadcasts_failed, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Toast.makeText(RPANActivity.this, + R.string.load_rpan_broadcasts_failed, Toast.LENGTH_SHORT).show(); + } + }); + } + + private void parseRPANBroadcasts(String response) { + Handler handler = new Handler(); + mExecutor.execute(() -> { + try { + ArrayList rpanBroadcasts = new ArrayList<>(); + JSONObject responseObject = new JSONObject(response); + String nextCursor = responseObject.getString(JSONUtils.NEXT_CURSOR_KEY); + + JSONArray dataArray = responseObject.getJSONArray(JSONUtils.DATA_KEY); + for (int i = 0; i < dataArray.length(); i++) { + JSONObject singleData = dataArray.getJSONObject(i); + JSONObject rpanPostObject = singleData.getJSONObject(JSONUtils.POST_KEY); + RPANBroadcast.RPANPost rpanPost = new RPANBroadcast.RPANPost( + rpanPostObject.getString(JSONUtils.TITLE_KEY), + rpanPostObject.getJSONObject(JSONUtils.SUBREDDIT_KEY).getString(JSONUtils.NAME_KEY), + rpanPostObject.getJSONObject(JSONUtils.SUBREDDIT_KEY).getJSONObject(JSONUtils.STYLES_KEY).getString(JSONUtils.ICON_KEY), + rpanPostObject.getJSONObject(JSONUtils.AUTHOR_INFO_KEY).getString(JSONUtils.NAME_KEY), + rpanPostObject.getInt(JSONUtils.SCORE_KEY), + rpanPostObject.getString(JSONUtils.VOTE_STATE_KEY), + rpanPostObject.getDouble(JSONUtils.UPVOTE_RATIO_CAMEL_CASE_KEY), + rpanPostObject.getString(JSONUtils.PERMALINK_KEY), + rpanPostObject.getJSONObject(JSONUtils.OUTBOUND_LINK_KEY).getString(JSONUtils.URL_KEY), + rpanPostObject.getBoolean(JSONUtils.IS_NSFW_KEY), + rpanPostObject.getBoolean(JSONUtils.IS_LOCKED_KEY), + rpanPostObject.getBoolean(JSONUtils.IS_ARCHIVED_KEY), + rpanPostObject.getBoolean(JSONUtils.IS_SPOILER), + rpanPostObject.getString(JSONUtils.SUGGESTED_COMMENT_SORT_CAMEL_CASE_KEY), + rpanPostObject.getString(JSONUtils.LIVE_COMMENTS_WEBSOCKET_KEY) + ); + + JSONObject rpanStreamObject = singleData.getJSONObject(JSONUtils.STREAM_KEY); + RPANBroadcast.RPANStream rpanStream = new RPANBroadcast.RPANStream( + rpanStreamObject.getString(JSONUtils.STREAM_ID_KEY), + rpanStreamObject.getString(JSONUtils.HLS_URL_KEY), + rpanStreamObject.getString(JSONUtils.THUMBNAIL_KEY), + rpanStreamObject.getInt(JSONUtils.WIDTH_KEY), + rpanStreamObject.getInt(JSONUtils.HEIGHT_KEY), + rpanStreamObject.getLong(JSONUtils.PUBLISH_AT_KEY), + rpanStreamObject.getString(JSONUtils.STATE_KEY) + ); + + rpanBroadcasts.add(new RPANBroadcast( + singleData.getInt(JSONUtils.UPVOTES_KEY), + singleData.getInt(JSONUtils.DOWNVOTES_KEY), + singleData.getInt(JSONUtils.UNIQUE_WATCHERS_KEY), + singleData.getInt(JSONUtils.CONTINUOUS_WATCHERS_KEY), + singleData.getInt(JSONUtils.TOTAL_CONTINUOUS_WATCHERS_KEY), + singleData.getBoolean(JSONUtils.CHAT_DISABLED_KEY), + singleData.getDouble(JSONUtils.BROADCAST_TIME_KEY), + singleData.getDouble(JSONUtils.ESTIMATED_REMAINING_TIME_KEY), + rpanPost, + rpanStream + )); + } + + handler.post(() -> { + RPANActivity.this.rpanBroadcasts = rpanBroadcasts; + RPANActivity.this.nextCursor = nextCursor; + + initializeViewPager(); + }); + } catch (JSONException e) { + e.printStackTrace(); + handler.post(() -> Toast.makeText(RPANActivity.this, + R.string.parse_rpan_broadcasts_failed, Toast.LENGTH_SHORT).show()); + } + }); + } + + private void initializeViewPager() { + sectionsPagerAdapter = new SectionsPagerAdapter(this); + viewPager2.setAdapter(sectionsPagerAdapter); + viewPager2.setOffscreenPageLimit(3); + viewPager2.setUserInputEnabled(!mSharedPreferences.getBoolean(SharedPreferencesUtils.DISABLE_SWIPING_BETWEEN_TABS, false)); + fixViewPager2Sensitivity(viewPager2); + } + + @Override + protected SharedPreferences getDefaultSharedPreferences() { + return mSharedPreferences; + } + + @Override + protected CustomThemeWrapper getCustomThemeWrapper() { + return mCustomThemeWrapper; + } + + @Override + protected void applyCustomTheme() { + + } + + private class SectionsPagerAdapter extends FragmentStateAdapter { + + public SectionsPagerAdapter(FragmentActivity fa) { + super(fa); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + ViewRPANBroadcastFragment fragment = new ViewRPANBroadcastFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelable(ViewRPANBroadcastFragment.EXTRA_RPAN_BROADCAST, rpanBroadcasts.get(position)); + fragment.setArguments(bundle); + return fragment; + } + + @Nullable + private Fragment getCurrentFragment() { + if (viewPager2 == null || getSupportFragmentManager() == null) { + return null; + } + return getSupportFragmentManager().findFragmentByTag("f" + viewPager2.getCurrentItem()); + } + + @Override + public int getItemCount() { + return rpanBroadcasts == null ? 0 : rpanBroadcasts.size(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/Strapi.java b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/Strapi.java new file mode 100644 index 00000000..2c8f4797 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/Strapi.java @@ -0,0 +1,12 @@ +package ml.docilealligator.infinityforreddit.apis; + +import java.util.Map; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.HeaderMap; + +public interface Strapi { + @GET("/broadcasts") + Call getAllBroadcasts(@HeaderMap Map headers); +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java new file mode 100644 index 00000000..3be8e7de --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java @@ -0,0 +1,225 @@ +package ml.docilealligator.infinityforreddit.fragments; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; + +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.source.BehindLiveWindowException; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; +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.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 javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.RPANBroadcast; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; + +public class ViewRPANBroadcastFragment extends Fragment { + + public static final String EXTRA_RPAN_BROADCAST = "ERB"; + private static final String IS_MUTE_STATE = "IMS"; + + @BindView(R.id.player_view_view_rpan_broadcast_fragment) + PlayerView playerView; + @BindView(R.id.progress_bar_view_rpan_broadcast_fragment) + ProgressBar progressBar; + @BindView(R.id.mute_exo_playback_control_view) + ImageButton muteButton; + @BindView(R.id.hd_exo_playback_control_view) + ImageButton hdButton; + @Inject + @Named("default") + SharedPreferences mSharedPreferences; + @Inject + @Named("current_account") + SharedPreferences mCurrentAccountSharedPreferences; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + @Inject + SimpleCache mSimpleCache; + private AppCompatActivity mActivity; + private RPANBroadcast rpanBroadcast; + private SimpleExoPlayer player; + private DefaultTrackSelector trackSelector; + private DataSource.Factory dataSourceFactory; + + private boolean wasPlaying; + private boolean isMute = false; + private long resumePosition = -1; + private boolean isDataSavingMode; + + public ViewRPANBroadcastFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View rootView = inflater.inflate(R.layout.fragment_view_rpan_broadcast, container, false); + + ((Infinity) mActivity.getApplication()).getAppComponent().inject(this); + + ButterKnife.bind(this, rootView); + + rpanBroadcast = getArguments().getParcelable(EXTRA_RPAN_BROADCAST); + + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); + trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); + player = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector); + playerView.setPlayer(player); + + dataSourceFactory = new DefaultHttpDataSourceFactory(Util.getUserAgent(mActivity, "Infinity")); + // Prepare the player with the source. + player.prepare(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(rpanBroadcast.rpanStream.hlsUrl))); + player.setRepeatMode(Player.REPEAT_MODE_ALL); + if (resumePosition > 0) { + player.seekTo(resumePosition); + } + player.setPlayWhenReady(true); + wasPlaying = true; + + boolean muteVideo = mSharedPreferences.getBoolean(SharedPreferencesUtils.MUTE_VIDEO, false) || + (mSharedPreferences.getBoolean(SharedPreferencesUtils.MUTE_NSFW_VIDEO, false) && rpanBroadcast.rpanPost.isNsfw); + + if (savedInstanceState != null) { + isMute = savedInstanceState.getBoolean(IS_MUTE_STATE); + if (isMute) { + player.setVolume(0f); + muteButton.setImageResource(R.drawable.ic_mute_24dp); + } else { + player.setVolume(1f); + muteButton.setImageResource(R.drawable.ic_unmute_24dp); + } + } else if (muteVideo) { + isMute = true; + player.setVolume(0f); + muteButton.setImageResource(R.drawable.ic_mute_24dp); + } else { + muteButton.setImageResource(R.drawable.ic_unmute_24dp); + } + + player.addListener(new Player.EventListener() { + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + if (!trackGroups.isEmpty()) { + if (isDataSavingMode) { + trackSelector.setParameters( + trackSelector.buildUponParameters() + .setMaxVideoSize(720, 720)); + } + + hdButton.setVisibility(View.VISIBLE); + hdButton.setOnClickListener(view -> { + TrackSelectionDialogBuilder build = new TrackSelectionDialogBuilder(mActivity, + getString(R.string.select_video_quality), trackSelector, 0); + build.setShowDisableOption(true); + build.setAllowAdaptiveSelections(false); + build.build().show(); + }); + + for (int i = 0; i < trackGroups.length; i++) { + String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; + if (mimeType != null && mimeType.contains("audio")) { + muteButton.setVisibility(View.VISIBLE); + muteButton.setOnClickListener(view -> { + if (isMute) { + isMute = false; + player.setVolume(1f); + muteButton.setImageResource(R.drawable.ic_unmute_24dp); + } else { + isMute = true; + player.setVolume(0f); + muteButton.setImageResource(R.drawable.ic_mute_24dp); + } + }); + break; + } + } + } else { + muteButton.setVisibility(View.GONE); + } + } + }); + + return rootView; + } + + private boolean isBehindLiveWindow(ExoPlaybackException e) { + if (e.type != ExoPlaybackException.TYPE_SOURCE) { + return false; + } + Throwable cause = e.getSourceException(); + while (cause != null) { + if (cause instanceof BehindLiveWindowException) { + return true; + } + cause = cause.getCause(); + } + return false; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(IS_MUTE_STATE, isMute); + } + + @Override + public void onStart() { + super.onStart(); + if (wasPlaying) { + player.setPlayWhenReady(true); + } + } + + @Override + public void onStop() { + super.onStop(); + wasPlaying = player.getPlayWhenReady(); + player.setPlayWhenReady(false); + } + + @Override + public void onDestroy() { + super.onDestroy(); + player.seekToDefaultPosition(); + player.stop(true); + player.release(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mActivity = (AppCompatActivity) context; + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/APIUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/APIUtils.java index fb49b49e..5ad14398 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/APIUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/APIUtils.java @@ -22,6 +22,7 @@ public class APIUtils { public static final String REDGIFS_API_BASE_URI = "https://api.redgifs.com/v1/gfycats/"; public static final String IMGUR_API_BASE_URI = "https://api.imgur.com/3/"; public static final String PUSHSHIFT_API_BASE_URI = "https://api.pushshift.io/"; + public static final String STRAPI_BASE_URI = "https://strapi.reddit.com"; public static final String CLIENT_ID_KEY = "client_id"; public static final String CLIENT_ID = "NOe2iKrPPzwscA"; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java index 8ba23bf4..579242a3 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java @@ -135,4 +135,31 @@ public class JSONUtils { public static final String WEBM_URL_KEY = "webmUrl"; public static final String UPVOTE_RATIO_KEY = "upvote_ratio"; public static final String INBOX_COUNT_KEY = "inbox_count"; + public static final String NEXT_CURSOR_KEY = "next_cursor"; + public static final String POST_KEY = "post"; + public static final String STYLES_KEY = "styles"; + public static final String AUTHOR_INFO_KEY= "authorInfo"; + public static final String VOTE_STATE_KEY = "voteState"; + public static final String UPVOTE_RATIO_CAMEL_CASE_KEY = "upvoteRatio"; + public static final String OUTBOUND_LINK_KEY = "outboundLink"; + public static final String IS_NSFW_KEY = "isNsfw"; + public static final String IS_LOCKED_KEY = "isLocked"; + public static final String IS_ARCHIVED_KEY = "isArchived"; + public static final String IS_SPOILER = "isSpoiler"; + public static final String SUGGESTED_COMMENT_SORT_CAMEL_CASE_KEY = "suggestedCommentSort"; + public static final String LIVE_COMMENTS_WEBSOCKET_KEY = "liveCommentsWebsocket"; + public static final String ICON_KEY = "icon"; + public static final String STREAM_KEY = "stream"; + public static final String STREAM_ID_KEY = "stream_id"; + public static final String THUMBNAIL_KEY = "thumbnail"; + public static final String PUBLISH_AT_KEY = "publish_at"; + public static final String STATE_KEY = "state"; + public static final String UPVOTES_KEY = "upvotes"; + public static final String DOWNVOTES_KEY = "downvotes"; + public static final String UNIQUE_WATCHERS_KEY = "unique_watchers"; + public static final String CONTINUOUS_WATCHERS_KEY = "continuous_watchers"; + public static final String TOTAL_CONTINUOUS_WATCHERS_KEY = "total_continuous_watchers"; + 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"; } diff --git a/app/src/main/res/layout/activity_rpanactivity.xml b/app/src/main/res/layout/activity_rpanactivity.xml new file mode 100644 index 00000000..8d1c85fb --- /dev/null +++ b/app/src/main/res/layout/activity_rpanactivity.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_rpan_broadcast.xml b/app/src/main/res/layout/fragment_view_rpan_broadcast.xml new file mode 100644 index 00000000..fdfe62c6 --- /dev/null +++ b/app/src/main/res/layout/fragment_view_rpan_broadcast.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79870453..1d6753ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Post Filter Users Select a Multireddit + RPAN Open navigation drawer Close navigation drawer @@ -1126,4 +1127,7 @@ Unable to get the bitmap of the image Unable to upload the image + Cannot load RPAN broadcasts + Cannot parse RPAN broadcasts +