Skip to content

Commit

Permalink
Match Livestreaming UX flow to odysee.com
Browse files Browse the repository at this point in the history
UX flow:
- Clicking the Upload button shows menu to pick Upload or Go Live
- Create livestreams with form like publishing
- Choose New Livestream or Upload Replay
  Replays shown in pages of 4 like on odysee.com
- Choose Anytime or Scheduled Time stream
- Clicking the Create button goes to livestreams dashboard (also
  accessible from user menu)
- Livestreams dashboard shows stream server and stream key, with button
  to copy
- Dashboard waits for created claim to finish confirming then enables
  Start Streaming button
- Clicking Start Streaming goes to activity that streams with HaishinKit
- Streaming activity sets FLAG_KEEP_SCREEN_ON in onCreate instead of
  when streaming starts. Also removes flag only onDestroy.

Changes of note:
- There are now 3 publish tasks:
  - PublishClaimTask uses the LBRY API
  - ReplayPublishTask uses the v1 Odysee Publish API
  - TusPublishTask uses the v2 Odysee Publish API (uses Tus for upload)
- Add buildPublishOptions function in Helper to create options map from
  Claim.

Fix: #93
Fix: #94
Fix (partial): #232 (app reloading after accepting permissions to
camera/audio when trying to go live)
  • Loading branch information
ktprograms committed Sep 23, 2022
1 parent 6295915 commit 6289456
Show file tree
Hide file tree
Showing 47 changed files with 3,477 additions and 866 deletions.
552 changes: 56 additions & 496 deletions app/src/main/java/com/odysee/app/GoLiveActivity.java

Large diffs are not rendered by default.

91 changes: 74 additions & 17 deletions app/src/main/java/com/odysee/app/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Menu;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
Expand All @@ -64,6 +65,7 @@
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
Expand Down Expand Up @@ -234,6 +236,8 @@
import com.odysee.app.ui.findcontent.FileViewFragment;
import com.odysee.app.ui.findcontent.FollowingFragment;
import com.odysee.app.ui.other.CreatorSettingsFragment;
import com.odysee.app.ui.publish.GoLiveFormFragment;
import com.odysee.app.ui.publish.LivestreamsFragment;
import com.odysee.app.ui.rewards.RewardVerificationFragment;
import com.odysee.app.ui.library.LibraryFragment;
import com.odysee.app.ui.library.PlaylistFragment;
Expand Down Expand Up @@ -373,6 +377,7 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
public static final String ACTION_NOW_PLAYING_CLAIM_UPDATED = "com.odysee.app.Broadcast.NowPlayingClaimUpdated";
public static final String ACTION_NOW_PLAYING_CLAIM_CLEARED = "com.odysee.app.Broadcast.NowPlayingClaimCleared";
public static final String ACTION_PUBLISH_SUCCESSFUL = "com.odysee.app.Broadcast.PublishSuccessful";
public static final String ACTION_LIVESTREAM_PUBLISH_SUCCESSFUL = "com.odysee.app.Broadcast.LivestreamPublishSuccessful";
public static final String ACTION_OPEN_ALL_CONTENT_TAG = "com.odysee.app.Broadcast.OpenAllContentTag";
public static final String ACTION_WALLET_BALANCE_UPDATED = "com.odysee.app.Broadcast.WalletBalanceUpdated";
public static final String ACTION_OPEN_CHANNEL_URL = "com.odysee.app.Broadcast.OpenChannelUrl";
Expand Down Expand Up @@ -792,7 +797,23 @@ public void onClick(View view) {
return;
}

showPublishFlow();
PopupMenu popup = new PopupMenu(MainActivity.this, view);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.menu_upload, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.action_upload) {
showPublishFlow();
return true;
} else if (item.getItemId() == R.id.action_go_live) {
showGoLiveFlow();
return true;
}
return false;
}
});
popup.show();
}
});

Expand Down Expand Up @@ -989,7 +1010,7 @@ public void onClick(View view) {
View buttonChangeDefaultChannel = customView.findViewById(R.id.button_change_default_channel);
View defaultChannelListParent = customView.findViewById(R.id.default_channel_list_layout);
ListView defaultChannelList = customView.findViewById(R.id.default_channel_list);
View buttonGoLive = customView.findViewById(R.id.button_go_live);
View buttonLivestreams = customView.findViewById(R.id.button_livestreams);
View buttonChannels = customView.findViewById(R.id.button_channels);
View buttonCreatorSettings = customView.findViewById(R.id.button_creator_settings);
View buttonPublishes = customView.findViewById(R.id.button_publishes);
Expand All @@ -1003,10 +1024,10 @@ public void onClick(View view) {
Account odyseeAccount = Helper.getOdyseeAccount(am.getAccounts());
final boolean isSignedIn = odyseeAccount != null;

buttonGoLive.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonChannels.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonCreatorSettings.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonPublishes.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonLivestreams.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonShowRewards.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonYouTubeSync.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
buttonSignOut.setVisibility(isSignedIn ? View.VISIBLE : View.GONE);
Expand Down Expand Up @@ -1087,8 +1108,8 @@ public void onClick(View v) {
defaultChannelList.requestLayout();

buttonChannels.setVisibility(View.GONE);
buttonGoLive.setVisibility(View.GONE);
buttonPublishes.setVisibility(View.GONE);
buttonLivestreams.setVisibility(View.GONE);
buttonShowRewards.setVisibility(View.GONE);
customView.findViewById(R.id.button_help_support).setVisibility(View.GONE);
customView.findViewById(R.id.button_app_settings).setVisibility(View.GONE);
Expand All @@ -1100,8 +1121,8 @@ public void onClick(View v) {
} else {
TransitionManager.beginDelayedTransition((ViewGroup) popupWindow.getContentView());
buttonChannels.setVisibility(View.VISIBLE);
buttonGoLive.setVisibility(View.VISIBLE);
buttonPublishes.setVisibility(View.VISIBLE);
buttonLivestreams.setVisibility(View.VISIBLE);
buttonShowRewards.setVisibility(View.VISIBLE);
customView.findViewById(R.id.button_help_support).setVisibility(View.VISIBLE);
customView.findViewById(R.id.button_app_settings).setVisibility(View.VISIBLE);
Expand All @@ -1124,14 +1145,6 @@ public void onItemClick(AdapterView<?> parent, View view, int position, long id)
}
});

buttonGoLive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
popupWindow.dismiss();
hideNotifications();
startActivity(new Intent(MainActivity.this, GoLiveActivity.class));
}
});
buttonChannels.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Expand All @@ -1156,6 +1169,14 @@ public void onClick(View v) {
openFragment(PublishesFragment.class, true, null);
}
});
buttonLivestreams.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
popupWindow.dismiss();
hideNotifications();
openFragment(LivestreamsFragment.class, true, null);
}
});
buttonShowRewards.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Expand Down Expand Up @@ -1292,9 +1313,14 @@ private void showPublishFlow() {
// Show PublishFragment.class
clearPlayingPlayer();
hideNotifications(); // Avoid showing Notifications fragment when clicking Publish when Notification panel is opened
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.main_activity_other_fragment, new PublishFragment(), "PUBLISH").addToBackStack("publish_claim").commit();
findViewById(R.id.fragment_container_main_activity).setVisibility(View.GONE);
openFragment(PublishFragment.class, true, null);
hideActionBar();
}

private void showGoLiveFlow() {
clearPlayingPlayer();
hideNotifications();
openFragment(GoLiveFormFragment.class, true, null);
hideActionBar();
}

Expand Down Expand Up @@ -1568,6 +1594,7 @@ public void openPublishesOnSuccessfulPublish() {
public void run() {
try {
getSupportFragmentManager().popBackStack();
getSupportFragmentManager().popBackStack(); // Pop PublishFragment (video picker)
openFragment(PublishesFragment.class, true, null);
} catch (IllegalStateException ex) {
// pass
Expand All @@ -1581,6 +1608,25 @@ public void run() {
});
}

public void openLivestreamsOnSuccessfulLivestreamPublish(Intent receivedIntent) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
getSupportFragmentManager().popBackStack();
openFragment(LivestreamsFragment.class, true, null);
} catch (IllegalStateException ex) {
// pass
try {
onBackPressed();
} catch (IllegalStateException iex) {
// if this fails on some devices. what's the solution?
}
}
}
});
}

public void openPublishForm(Claim claim) {
Map<String, Object> params = new HashMap<>();
if (claim != null) {
Expand All @@ -1589,6 +1635,14 @@ public void openPublishForm(Claim claim) {
openFragment(PublishFormFragment.class, true, params);
}

public void openGoLiveForm(Claim claim) {
Map<String, Object> params = new HashMap<>();
if (claim != null) {
params.put("claim", claim);
}
openFragment(GoLiveFormFragment.class, true, params);
}

public void openChannelUrl(String url, String source) {
Map<String, Object> params = new HashMap<>();
params.put("url", url);
Expand Down Expand Up @@ -3501,6 +3555,7 @@ private void registerRequestsReceiver() {
intentFilter.addAction(ACTION_OPEN_WALLET_PAGE);
intentFilter.addAction(ACTION_OPEN_REWARDS_PAGE);
intentFilter.addAction(ACTION_PUBLISH_SUCCESSFUL);
intentFilter.addAction(ACTION_LIVESTREAM_PUBLISH_SUCCESSFUL);
intentFilter.addAction(ACTION_SAVE_SHARED_USER_STATE);
intentFilter.addAction(LbrynetMessagingService.ACTION_NOTIFICATION_RECEIVED);
requestsReceiver = new BroadcastReceiver() {
Expand All @@ -3517,6 +3572,8 @@ public void onReceive(Context context, Intent intent) {
saveSharedUserState();
} else if (ACTION_PUBLISH_SUCCESSFUL.equalsIgnoreCase(action)) {
openPublishesOnSuccessfulPublish();
} else if (ACTION_LIVESTREAM_PUBLISH_SUCCESSFUL.equalsIgnoreCase(action)) {
openLivestreamsOnSuccessfulLivestreamPublish(intent);
} else if (LbrynetMessagingService.ACTION_NOTIFICATION_RECEIVED.equalsIgnoreCase(action)) {
handleNotificationReceived(intent);
}
Expand Down
97 changes: 97 additions & 0 deletions app/src/main/java/com/odysee/app/adapter/ReplaysPagerAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.odysee.app.adapter;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import com.odysee.app.model.LivestreamReplay;
import com.odysee.app.ui.publish.ReplaysPageFragment;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ReplaysPagerAdapter extends FragmentStateAdapter {
private static final int PAGE_SIZE = 4;

private final List<LivestreamReplay> replays;
private final SelectedReplayManager manager;

// Needed because default getItemId() uses position so fragments
// don't get changed when calling notifyDataSetChanged().
private long idCounter = 0;
private List<Long> itemIds;

public ReplaysPagerAdapter(FragmentActivity activity, List<LivestreamReplay> replays, SelectedReplayManager manager) {
super(activity);
this.replays = replays;
this.manager = manager;

LivestreamReplay selectedReplay = manager.getSelectedReplay();
if (selectedReplay != null) {
for (LivestreamReplay replay : replays) {
if (replay.getUrl().equals(selectedReplay.getUrl())) {
replay.setSelected(true);
}
}
}

this.itemIds = generateItemIds();
RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
itemIds = generateItemIds();
}
};
registerAdapterDataObserver(observer);
}

private List<Long> generateItemIds() {
return IntStream.range(0, getItemCount())
.mapToObj(unused -> idCounter++)
.collect(Collectors.toList());
}

@NonNull
@Override
public Fragment createFragment(int position) {
ReplaysPageFragment fragment = new ReplaysPageFragment();
fragment.setReplays(replays.subList(
position * 4, Math.min((position * 4) + 4, replays.size())));
fragment.setListener(new ReplaysPageFragment.ReplaySelectedListener() {
@Override
public void onReplaySelected(LivestreamReplay replay) {
LivestreamReplay selectedReplay = manager.getSelectedReplay();
if (selectedReplay != null) {
selectedReplay.setSelected(false);
}
replay.setSelected(true);
manager.setSelectedReplay(replay);
notifyDataSetChanged();
}
});
return fragment;
}

@Override
public int getItemCount() {
return (int) Math.ceil(replays.size() / (double) PAGE_SIZE);
}

@Override
public long getItemId(int position) {
return itemIds.get(position);
}

@Override
public boolean containsItem(long itemId) {
return itemIds.contains(itemId);
}

public interface SelectedReplayManager {
LivestreamReplay getSelectedReplay();
void setSelectedReplay(LivestreamReplay replay);
}
}
35 changes: 35 additions & 0 deletions app/src/main/java/com/odysee/app/model/LivestreamReplay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.odysee.app.model;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

import org.json.JSONObject;

import java.util.Date;
import java.util.List;

import lombok.Data;

@Data
public class LivestreamReplay {
private String status;
private String percentComplete;
@SerializedName("URL")
private String url;
@SerializedName("ThumbnailURLs")
private List<String> thumbnailUrls;
private long duration;
private Date created;

public boolean selected;

public static LivestreamReplay fromJSONObject(JSONObject replayObject) {
String replayJson = replayObject.toString();

Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();

return gson.fromJson(replayJson, LivestreamReplay.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.odysee.app.tasks;

import com.odysee.app.model.LivestreamReplay;

import java.util.List;

public interface LivestreamReplaysResultHandler {
void onSuccess(List<LivestreamReplay> replays);
void onError(Exception error);
}
Loading

0 comments on commit 6289456

Please sign in to comment.