Skip to content

Commit

Permalink
Make DownloadedVideosDb more reactive
Browse files Browse the repository at this point in the history
  • Loading branch information
gzsombor committed Jan 20, 2021
1 parent 384cd49 commit 25cf25a
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ public void playVideoOnChromecast(final YouTubeVideo video, final int position)
compositeDisposable.add(
video.getDesiredStream(new GetDesiredStreamListener() {
@Override
public void onGetDesiredStream(StreamInfo desiredStream) {
public void onGetDesiredStream(StreamInfo desiredStream, YouTubeVideo video) {
if(mCastSession == null)
return;
Gson gson = new Gson();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import free.rm.skytube.businessobjects.db.DatabaseResult;
import free.rm.skytube.businessobjects.db.DownloadedVideosDb;
import free.rm.skytube.businessobjects.interfaces.GetDesiredStreamListener;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;

import static free.rm.skytube.app.SkyTubeApp.getContext;
Expand Down Expand Up @@ -177,7 +178,7 @@ public YouTubeVideo(Video video) throws IllegalArgumentException {
}
}

private void setViewCount(BigInteger viewsCountInt) {
public void setViewCount(BigInteger viewsCountInt) {
this.viewsCountInt = viewsCountInt;
this.viewsCount = String.format(getStr(R.string.views), viewsCountInt);
}
Expand Down Expand Up @@ -509,16 +510,7 @@ public Disposable downloadVideo(final Context context) {
// if description is not yet downloaded, get it, and call the download action again.
return getDesiredStream(new GetDesiredStreamListener() {
@Override
public void onGetDesiredStream(StreamInfo streamInfo) {
description = streamInfo.getDescription().getContent();
long like = streamInfo.getLikeCount();
long dislike = streamInfo.getDislikeCount();
setLikeDislikeCount(like >= 0 ? like : null, dislike >= 0 ? dislike : null);

long views = streamInfo.getViewCount();
if (views >= 0) {
setViewCount(BigInteger.valueOf(views));
}
public void onGetDesiredStream(StreamInfo streamInfo, YouTubeVideo video) {

final Settings settings = SkyTubeApp.getSettings();
StreamSelectionPolicy selectionPolicy = settings.getDesiredVideoResolution(true);
Expand Down Expand Up @@ -561,24 +553,26 @@ public void onGetDesiredStreamError(Throwable throwable) {
/**
* Play the video using an external app
*/
public void playVideoExternally(Context context) {
DownloadedVideosDb.Status fileStatus = DownloadedVideosDb.getVideoDownloadsDb().getDownloadedFileStatus(context, getVideoId());
if (fileStatus.getLocalVideoFile() != null) {
Uri uri;
File file = fileStatus.getLocalVideoFile();
try {
uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
} catch (Exception e) {
Logger.e(YouTubeVideo.this, "Error accessing path: " + file + ", message:" + e.getMessage(), e);
uri = fileStatus.getUri();
public Single<DownloadedVideosDb.Status> playVideoExternally(Context context) {
return DownloadedVideosDb.getVideoDownloadsDb().getDownloadedFileStatus(context, getVideoId()).map(fileStatus -> {
if (fileStatus.getLocalVideoFile() != null) {
Uri uri;
File file = fileStatus.getLocalVideoFile();
try {
uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
} catch (Exception e) {
Logger.e(YouTubeVideo.this, "Error accessing path: " + file + ", message:" + e.getMessage(), e);
uri = fileStatus.getUri();
}
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
} else {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getVideoUrl()));
context.startActivity(browserIntent);
}
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
return;
}
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getVideoUrl()));
context.startActivity(browserIntent);
return fileStatus;
});
}

////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.schabi.newpipe.extractor.exceptions.ExtractionException;

import java.io.IOException;
import java.math.BigInteger;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -35,6 +36,7 @@
import free.rm.skytube.businessobjects.YouTube.newpipe.ContentId;
import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeException;
import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService;
import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeUtils;
import free.rm.skytube.businessobjects.YouTube.newpipe.PlaylistPager;
import free.rm.skytube.businessobjects.db.SubscriptionsDb;
import free.rm.skytube.businessobjects.interfaces.GetDesiredStreamListener;
Expand Down Expand Up @@ -300,8 +302,19 @@ public static Disposable getVideoStream(@NonNull YouTubeVideo youTubeVideo,
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamInfo -> {
if (streamInfo != null){
listener.onGetDesiredStream(streamInfo);
if (streamInfo != null) {
long like = streamInfo.getLikeCount();
long dislike = streamInfo.getDislikeCount();
youTubeVideo.setLikeDislikeCount(like >= 0 ? like : null, dislike >= 0 ? dislike : null);

long views = streamInfo.getViewCount();
if (views >= 0) {
youTubeVideo.setViewCount(BigInteger.valueOf(views));
}

youTubeVideo.setLikeDislikeCount(streamInfo.getLikeCount(), streamInfo.getDislikeCount());
youTubeVideo.setDescription(NewPipeUtils.filterHtml(streamInfo.getDescription()));
listener.onGetDesiredStream(streamInfo, youTubeVideo);
}
}, listener::onGetDesiredStreamError);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@

import androidx.annotation.NonNull;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.safety.Whitelist;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
Expand All @@ -42,10 +38,8 @@
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;

import java.io.IOException;
import java.time.ZoneId;
Expand Down Expand Up @@ -254,7 +248,7 @@ private YouTubeChannel createInternalChannelFromFeed(FeedExtractor extractor) th
}

private YouTubeChannel createInternalChannel(ChannelExtractor extractor) throws ParsingException {
return new YouTubeChannel(extractor.getId(), extractor.getName(), filterHtml(extractor.getDescription()),
return new YouTubeChannel(extractor.getId(), extractor.getName(), NewPipeUtils.filterHtml(extractor.getDescription()),
extractor.getAvatarUrl(), extractor.getBannerUrl(), getSubscriberCount(extractor), false, 0, System.currentTimeMillis());
}

Expand Down Expand Up @@ -326,7 +320,7 @@ public YouTubeVideo getDetails(String videoId) throws ExtractionException, IOExc
viewCount = 0;
}

YouTubeVideo video = new YouTubeVideo(extractor.getId(), extractor.getName(), filterHtml(extractor.getDescription()),
YouTubeVideo video = new YouTubeVideo(extractor.getId(), extractor.getName(), NewPipeUtils.filterHtml(extractor.getDescription()),
extractor.getLength(), new YouTubeChannel(extractor.getUploaderUrl(), extractor.getUploaderName()),
viewCount, uploadDate.zonedDateTime, uploadDate.exact, extractor.getThumbnailUrl());
try {
Expand Down Expand Up @@ -372,22 +366,6 @@ static String getThumbnailUrl(String id) {
return "https://i.ytimg.com/vi/" + id + "/hqdefault.jpg";
}

private String filterHtml(String content) {
return Jsoup.clean(content, "", Whitelist.basic(), new OutputSettings().prettyPrint(false));
}

private String filterHtml(Description description) {
String result;
if (description.getType() == Description.HTML) {
result = filterHtml(description.getContent());
} else {
result = description.getContent();
}
if (DEBUG_LOG) {
Logger.d(this, "filterHtml %s -> %s", description, result);
}
return result;
}

public VideoPager getSearchResult(String query) throws NewPipeException {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SkyTube
* Copyright (C) 2021 Zsombor Gegesy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation (version 3 of the License).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package free.rm.skytube.businessobjects.YouTube.newpipe;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import org.schabi.newpipe.extractor.stream.Description;

public class NewPipeUtils {
private NewPipeUtils() {}

public static String filterHtml(String content) {
return Jsoup.clean(content, "", Whitelist.basic(), new Document.OutputSettings().prettyPrint(false));
}

public static String filterHtml(Description description) {
String result;
if (description.getType() == Description.HTML) {
result = filterHtml(description.getContent());
} else {
result = description.getContent();
}
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeVideo;
import free.rm.skytube.businessobjects.YouTube.newpipe.VideoId;
import free.rm.skytube.businessobjects.interfaces.OrderableDatabase;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;

/**
* A database (DB) that stores user's downloaded videos.
Expand Down Expand Up @@ -239,15 +243,12 @@ private boolean remove(String videoId) {
return (rowsDeleted >= 0);
}

public boolean remove(YouTubeVideo video) {
return remove(video.getId());
}

/**
* Remove local copy of this video, and delete it from the VideoDownloads DB.
* @return
*/
public void removeDownload(Context ctx, VideoId videoId) {
try {
public @NonNull Completable removeDownload(Context ctx, VideoId videoId) {
return Completable.fromCallable(() -> {
Status status = getVideoFileStatus(videoId);
Log.i(TAG, "removeDownload for " + videoId + " -> " + status);
if (status != null) {
Expand All @@ -259,9 +260,13 @@ public void removeDownload(Context ctx, VideoId videoId) {
removeParentFolderIfEmpty(status, settings.getDownloadParentFolder());
}
}
} catch (FileDeletionFailed exc) {
displayError(ctx, exc);
}
return status;
}).observeOn(AndroidSchedulers.mainThread())
.doOnError(exception -> {
displayGenericError(ctx, exception);
})
.onErrorComplete()
.subscribeOn(Schedulers.io());
}

private void removeParentFolderIfEmpty(Status file, File downloadParentFolder) {
Expand Down Expand Up @@ -333,28 +338,31 @@ private Uri getUri(Cursor cursor, int columnIndex) {
* @param videoId the id of the video
* @return the status, never null
*/
public @NonNull Status getVideoFileUriAndValidate(@NonNull VideoId videoId) throws FileDeletionFailed {
Status downloadStatus = getVideoFileStatus(videoId);
if (downloadStatus != null) {
File localVideo = downloadStatus.getLocalVideoFile();
if (localVideo != null) {
if (!localVideo.exists()) {
deleteIfExists(downloadStatus.getLocalAudioFile());
remove(videoId.getId());
return new Status(null, null, true);
private @NonNull Single<Status> getVideoFileUriAndValidate(@NonNull VideoId videoId) {
return Single.fromCallable( () -> {
Status downloadStatus = getVideoFileStatus(videoId);
if (downloadStatus != null) {
File localVideo = downloadStatus.getLocalVideoFile();
if (localVideo != null) {
if (!localVideo.exists()) {
deleteIfExists(downloadStatus.getLocalAudioFile());
remove(videoId.getId());
return new Status(null, null, true);
}
}
}
File localAudio = downloadStatus.getLocalAudioFile();
if (localAudio != null) {
if (!localAudio.exists()) {
deleteIfExists(downloadStatus.getLocalVideoFile());
remove(videoId.getId());
return new Status(null, null, true);
File localAudio = downloadStatus.getLocalAudioFile();
if (localAudio != null) {
if (!localAudio.exists()) {
deleteIfExists(downloadStatus.getLocalVideoFile());
remove(videoId.getId());
return new Status(null, null, true);
}
}
return downloadStatus;
}
return downloadStatus;
}
return new Status(null,null, false);
return new Status(null, null, false);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

/**
Expand All @@ -363,12 +371,18 @@ private Uri getUri(Cursor cursor, int columnIndex) {
*
* @return Status object - never null
*/
public @NonNull DownloadedVideosDb.Status getDownloadedFileStatus(Context context, @NonNull VideoId videoId) {
try {
return getVideoFileUriAndValidate(videoId);
} catch (DownloadedVideosDb.FileDeletionFailed exc) {
displayError(context, exc);
public @NonNull Single<Status> getDownloadedFileStatus(Context context, @NonNull VideoId videoId) {
return getVideoFileUriAndValidate(videoId).onErrorReturn(error -> {
displayGenericError(context, error);
return new Status(null, null, true);
});
}

private void displayGenericError(Context context, Throwable exception) {
if (exception instanceof FileDeletionFailed) {
displayError(context, (FileDeletionFailed) exception);
} else {
Log.e(TAG, "Exception : "+ exception.getMessage(), exception);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.schabi.newpipe.extractor.stream.StreamInfo;

import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeVideo;
import free.rm.skytube.businessobjects.YouTube.VideoStream.StreamMetaData;

/**
Expand All @@ -14,7 +15,7 @@ public interface GetDesiredStreamListener {
*
* @param streamInfo The retrieved video's Uri.
*/
void onGetDesiredStream(StreamInfo streamInfo);
void onGetDesiredStream(StreamInfo streamInfo, YouTubeVideo video);

/**
* Called if an error occurred while retrieving the video's Uri.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ private void onOptionsButtonClick(final View view, YouTubeVideo youTubeVideo) {
popupMenu.setOnMenuItemClickListener(item -> {
switch(item.getItemId()) {
case R.id.menu_open_video_with:
youTubeVideo.playVideoExternally(context);
compositeDisposable.add(youTubeVideo.playVideoExternally(context).subscribe());
return true;
case R.id.share:
youTubeVideo.shareVideo(view.getContext());
Expand Down Expand Up @@ -333,7 +333,8 @@ private void onOptionsButtonClick(final View view, YouTubeVideo youTubeVideo) {
context.startActivity(i);
return true;
case R.id.delete_download:
DownloadedVideosDb.getVideoDownloadsDb().removeDownload(context, youTubeVideo.getVideoId());
compositeDisposable.add(
DownloadedVideosDb.getVideoDownloadsDb().removeDownload(context, youTubeVideo.getVideoId()).subscribe());
return true;
case R.id.download_video:
final Policy decision = new MobileNetworkWarningDialog(view.getContext())
Expand Down
Loading

0 comments on commit 25cf25a

Please sign in to comment.