diff --git a/app/build.gradle b/app/build.gradle index 85a9a832..d3fab451 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId "io.lbry.browser" minSdkVersion 21 targetSdkVersion 29 - versionCode 1514 - versionName "0.15.14" + versionCode 1515 + versionName "0.15.15" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -96,7 +96,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - __32bitImplementation 'io.lbry:lbrysdk32:0.76.0' - __64bitImplementation 'io.lbry:lbrysdk64:0.76.0' + __32bitImplementation 'io.lbry:lbrysdk32:0.79.0' + __64bitImplementation 'io.lbry:lbrysdk64:0.79.0' } diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 0f5ad4d5..c5c7a819 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -301,7 +301,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static final String SECURE_VALUE_KEY_SAVED_PASSWORD = "io.lbry.browser.PX"; public static final String SECURE_VALUE_FIRST_RUN_PASSWORD = "firstRunPassword"; - private static final String TAG = "io.lbry.browser.Main"; + private static final String TAG = "LbryMain"; private NavigationMenuAdapter navMenuAdapter; private UrlSuggestionListAdapter urlSuggestionListAdapter; @@ -1548,7 +1548,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); - syncWalletAndLoadPreferences(); scheduleWalletBalanceUpdate(); scheduleWalletSyncTask(); fetchOwnChannels(); @@ -1766,7 +1765,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener //openNavFragments.get MergeSubscriptionsTask mergeTask = new MergeSubscriptionsTask( subscriptions, - !initialSubscriptionMergeDone(), + initialSubscriptionMergeDone(), MainActivity.this, new MergeSubscriptionsTask.MergeSubscriptionsHandler() { @Override public void onSuccess(List subscriptions, List diff) { @@ -1781,7 +1780,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener for (Fragment fragment : openNavFragments.values()) { if (fragment instanceof FollowingFragment) { // reload local subscriptions - ((FollowingFragment) fragment).fetchLoadedSubscriptions(true); + Lbryio.cacheResolvedSubscriptions.clear(); + FollowingFragment followingFragment = (FollowingFragment) fragment; + followingFragment.fetchLoadedSubscriptions(true); } } } @@ -1919,7 +1920,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public void onSyncGetSuccess(WalletSync walletSync) { Lbryio.lastWalletSync = walletSync; Lbryio.lastRemoteHash = walletSync.getHash(); - loadSharedUserState(); } @Override @@ -2625,7 +2625,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener LbryAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_OPEN, bundle); } - private void registerServiceActionsReceiver() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT); @@ -2787,9 +2786,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener JSONObject startupStatus = status.getJSONObject("startup_status"); sdkReady = startupStatus.getBoolean("file_manager") && startupStatus.getBoolean("wallet"); } - } catch (ConnectException ex) { - // pass - } catch (JSONException ex) { + } catch (ConnectException | JSONException ex) { // pass } diff --git a/app/src/main/java/io/lbry/browser/tasks/BufferEventTask.java b/app/src/main/java/io/lbry/browser/tasks/BufferEventTask.java new file mode 100644 index 00000000..826e392f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/BufferEventTask.java @@ -0,0 +1,65 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; +import android.util.Log; + +import org.json.JSONObject; + +import java.util.concurrent.TimeUnit; + +import io.lbry.browser.utils.Helper; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class BufferEventTask extends AsyncTask { + private static final String TAG = "LbryBufferEvent"; + private static final String ENDPOINT = "https://collector-service.api.lbry.tv/api/v1/events/video"; + + private String streamUrl; + private String userIdHash; + private long streamDuration; + private long streamPosition; + private long bufferDuration; + + public BufferEventTask(String streamUrl, long streamDuration, long streamPosition, long bufferDuration, String userIdHash) { + this.streamUrl = streamUrl; + this.bufferDuration = bufferDuration; + this.streamDuration = streamDuration; + this.streamPosition = streamPosition; + this.userIdHash = userIdHash; + } + + protected Void doInBackground(Void... params) { + JSONObject requestBody = new JSONObject(); + JSONObject data = new JSONObject(); + try { + data.put("url", streamUrl); + data.put("position", streamPosition); + data.put("stream_duration", streamDuration); + //data.put("duration", bufferDuration); + + requestBody.put("device", "android"); + requestBody.put("type", "buffering"); + requestBody.put("client", userIdHash); + requestBody.put("data", data); + + RequestBody body = RequestBody.create(requestBody.toString(), Helper.JSON_MEDIA_TYPE); + Request request = new Request.Builder().url(ENDPOINT).post(body).build(); + OkHttpClient client = new OkHttpClient.Builder(). + writeTimeout(60, TimeUnit.SECONDS). + readTimeout(60, TimeUnit.SECONDS). + build(); + + Response response = client.newCall(request).execute(); + String responseString = response.body().string(); + Log.d(TAG, String.format("buffer event sent: %s", responseString)); + } catch (Exception ex) { + // we don't want to fail if a buffer event fails to register + Log.d(TAG, String.format("buffer event log failed: %s", ex.getMessage()), ex); + } + + return null; + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java b/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java index c82d728a..2d516e13 100644 --- a/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java @@ -73,6 +73,7 @@ public class MergeSubscriptionsTask extends AsyncTask remoteUnsubs = new ArrayList<>(); + List finalRemoteSubs = new ArrayList<>(); + if (remoteSubs.size() > 0) { + for (int i = 0; i < remoteSubs.size(); i++) { + Subscription sub = remoteSubs.get(i); + if (!combined.contains(sub)) { Map options = new HashMap<>(); - String channelClaimId = uri.getChannelClaimId(); - String channelName = Helper.normalizeChannelName(local.getChannelName()); - if (!Helper.isNullOrEmpty(channelClaimId) && !Helper.isNullOrEmpty(channelName)) { - options.put("claim_id", channelClaimId); - options.put("channel_name", channelName); - Lbryio.parseResponse(Lbryio.call("subscription", "new", options, context)); + LbryUri uri = LbryUri.tryParse(sub.getUrl()); + if (uri != null) { + options.put("claim_id", uri.getChannelClaimId()); + Lbryio.parseResponse(Lbryio.call("subscription", "delete", options, context)); + remoteUnsubs.add(sub); + } else { + finalRemoteSubs.add(sub); } - } catch (LbryUriException | LbryioRequestException | LbryioResponseException ex) { - // pass - Log.e(TAG, String.format("subscription/new failed: %s", ex.getMessage()), ex); } } } @@ -114,13 +113,13 @@ public class MergeSubscriptionsTask extends AsyncTask { if (applySyncChanges) { if (applySyncSuccessful) { handler.onSyncApplySuccess(syncHash, syncData); - } else { + } else if (syncApplyError != null) { handler.onSyncApplyError(syncApplyError); } } diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java index 4f7a5b71..9adec2d7 100644 --- a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java @@ -120,6 +120,7 @@ import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.WalletBalance; import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.BufferEventTask; import io.lbry.browser.tasks.CommentCreateWithTipTask; import io.lbry.browser.tasks.CommentListHandler; import io.lbry.browser.tasks.CommentListTask; @@ -165,6 +166,7 @@ public class FileViewFragment extends BaseFragment implements WalletBalanceListener { private static final int RELATED_CONTENT_SIZE = 16; private static final String DEFAULT_PLAYBACK_SPEED = "1x"; + private static final String CDN_PREFIX = "https://cdn.lbryplayer.xyz"; private PlayerControlView castControlView; private Player currentPlayer; @@ -292,6 +294,27 @@ public class FileViewFragment extends BaseFragment implements loadingNewClaim = false; } } else if (playbackState == Player.STATE_BUFFERING) { + if (MainActivity.appPlayer != null && MainActivity.appPlayer.getCurrentPosition() > 0) { + // we only want to log a buffer event after the media has already started playing + String mediaSourceUrl = getStreamingUrl(); + long duration = MainActivity.appPlayer.getDuration(); + long position = MainActivity.appPlayer.getCurrentPosition(); + // TODO: Determine a hash for the userId + String userIdHash = Helper.SHA256(Lbryio.currentUser != null ? String.valueOf(Lbryio.currentUser.getId()) : "0"); + if (mediaSourceUrl.startsWith(CDN_PREFIX)) { + BufferEventTask bufferEvent = new BufferEventTask(claim.getPermanentUrl(), duration, position, 1, userIdHash); + bufferEvent.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + } else { + // sdk stream buffer events should be handled differently + Bundle bundle = new Bundle(); + bundle.putString("url", claim.getPermanentUrl()); + bundle.putLong("stream_duration", duration); + bundle.putLong("stream_position", position); + bundle.putString("user_id_hash", userIdHash); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_BUFFER, bundle); + } + } + showBuffering(); } else { hideBuffering(); diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index a2883ca2..eac70533 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -30,6 +30,8 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.gms.common.util.Hex; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -37,6 +39,9 @@ import org.json.JSONObject; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -766,4 +771,14 @@ public final class Helper { } return id.toString(); } + + public static String SHA256(String value) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(value.getBytes("UTF-8")); + return Hex.bytesToStringLowercase(hash); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { + return null; + } + } } diff --git a/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java b/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java index 609a920a..e3a35d8a 100644 --- a/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java +++ b/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java @@ -9,6 +9,7 @@ public class LbryAnalytics { public static final String EVENT_APP_ERROR = "app_error"; public static final String EVENT_APP_LAUNCH = "app_launch"; public static final String EVENT_COMMENT_CREATE = "comment_create"; + public static final String EVENT_BUFFER = "buffer"; public static final String EVENT_EMAIL_ADDED = "email_added"; public static final String EVENT_EMAIL_VERIFIED = "email_verified"; public static final String EVENT_FIRST_RUN_COMPLETED = "first_run_completed";