remove torrent lib

This commit is contained in:
Thomas 2022-05-13 16:41:58 +02:00
parent 1c6f7858ca
commit 7c695f5f82
39 changed files with 55 additions and 1971 deletions

View File

@ -148,7 +148,6 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation project(path: ':torrentStream')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -84,12 +84,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.frostwire.jlibtorrent.SessionManager;
import com.github.se_bastiaan.torrentstream.StreamStatus;
import com.github.se_bastiaan.torrentstream.Torrent;
import com.github.se_bastiaan.torrentstream.TorrentOptions;
import com.github.se_bastiaan.torrentstream.TorrentStream;
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
import com.github.vkay94.dtpv.youtube.YouTubeOverlay;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
@ -110,9 +104,7 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.DefaultTimeBar;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
@ -127,8 +119,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -177,7 +167,7 @@ import app.fedilab.fedilabtube.webview.MastalabWebViewClient;
import es.dmoral.toasty.Toasty;
public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction {
public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction {
public static String video_id;
public static List<String> playedVideos = new ArrayList<>();
@ -203,7 +193,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
private BroadcastReceiver mPowerKeyReceiver = null;
private boolean isPlayInMinimized;
private VideoData.Video nextVideo;
private TorrentStream torrentStream;
private String show_more_content;
private videoOrientation videoOrientationType;
private int initialOrientation;
@ -231,74 +220,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
}
}
@Override
public void onStreamPrepared(Torrent torrent) {
Log.v(Helper.TAG,"onStreamPrepared: " + torrent);
torrent.startDownload();
}
@Override
public void onStreamStarted(Torrent torrent) {
Log.v(Helper.TAG,"onStreamStarted: " + torrent);
}
@Override
public void onStreamError(Torrent torrent, Exception e) {
Log.v(Helper.TAG,"onStreamError: " + e.getMessage());
e.printStackTrace();
}
@Override
public void onStreamReady(Torrent torrent) {
Log.v(Helper.TAG,"onStreamReady: " + torrent);
if (torrent.getVideoFile() != null) {
for (int i = 0; i < torrent.getFileNames().length; i++) {
torrent.getTorrentHandle().renameFile(i, torrent.getFileNames()[0].replaceAll("[^a-zA-Z0-9/.-]", "_"));
}
startStream(peertube, torrent.getVideoFile().getAbsolutePath().replaceAll("[^a-zA-Z0-9/.-]", "_"), null, autoPlay, -1, null, null, true);
PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller);
ConstraintLayout torrent_info = controlView.findViewById(R.id.torrent_info);
TextView dowload_rate = controlView.findViewById(R.id.dowload_rate);
TextView upload_rate = controlView.findViewById(R.id.upload_rate);
torrent_info.setVisibility(View.VISIBLE);
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SessionManager sessionManager = torrentStream.getSessionManager();
if (sessionManager != null) {
long upload = sessionManager.uploadRate();
long download = sessionManager.downloadRate();
runOnUiThread(() -> {
dowload_rate.setText(String.format("▼ %s", Helper.rateSize(PeertubeActivity.this, download)));
upload_rate.setText(String.format("▲ %s", Helper.rateSize(PeertubeActivity.this, upload)));
});
}
}
}, 0, 1000);
} else {
Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
}
@Override
public void onStreamProgress(Torrent torrent, StreamStatus status) {
Log.v(Helper.TAG,"onStreamProgress: " + torrent);
if (binding != null) {
PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller);
TextView peers_number = controlView.findViewById(R.id.peers_number);
peers_number.setText(getString(R.string.peers, status.peers));
}
}
@Override
public void onStreamStopped() {
Log.v(Helper.TAG,"onStreamStopped: ");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -315,16 +236,9 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
Helper.loadAvatar(PeertubeActivity.this, account, binding.myPp);
}
isRemote = false;
TorrentOptions torrentOptions = new TorrentOptions.Builder()
.saveLocation(getCacheDir() + "/torrent/")
.autoDownload(true)
.removeFilesAfterStop(true)
.build();
fullScreenMode = false;
torrentStream = TorrentStream.init(torrentOptions);
torrentStream.addListener(PeertubeActivity.this);
initialOrientation = getResources().getConfiguration().orientation;
if (Helper.isTablet(PeertubeActivity.this)) {
@ -1180,7 +1094,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
startStream(
apiResponse.getPeertubes().get(0),
null,
null,
autoPlay, position, null, null, true);
player.prepare();
player.setPlayWhenReady(autoPlay);
@ -1273,83 +1186,65 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
* Manage video to play with different factors
*
* @param video VideoData.Video
* @param localTorrentUrl String if not null that will be used
* @param resolution String the current resolution asked
* @param autoPlay boolean
* @param position int current position
* @param subtitles Uri uri for subtitles
* @param lang String ("en","fr", etc.)
*/
private void stream(VideoData.Video video, String localTorrentUrl, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) {
videoURL = localTorrentUrl == null ? video.getFileUrl(resolution, PeertubeActivity.this) : localTorrentUrl;
private void stream(VideoData.Video video, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) {
videoURL = video.getFileUrl(resolution, PeertubeActivity.this);
Log.v(Helper.TAG,">>>>>>>>videoURL: " + videoURL );
if (subtitles != null) {
subtitlesStr = subtitles.toString();
}
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB);
if (videoURL != null && (videoURL.endsWith(".torrent") || videoURL.startsWith("magnet"))) {
torrentStream.startStream(videoURL);
return;
} else {
ProgressiveMediaSource videoSource = null;
HlsMediaSource hlsMediaSource = null;
SingleSampleMediaSource subtitleSource = null;
DataSource.Factory dataSourceFactory = null;
if (localTorrentUrl != null) {
DataSpec dataSpec = new DataSpec(Uri.fromFile(new java.io.File(localTorrentUrl)));
FileDataSource fileDataSource = new FileDataSource();
try {
fileDataSource.open(dataSpec);
dataSourceFactory = () -> fileDataSource;
} catch (FileDataSource.FileDataSourceException e) {
e.printStackTrace();
}
}
if (video_cache == 0 || dataSourceFactory != null || video.isLive()) {
if (dataSourceFactory == null) {
dataSourceFactory = new DefaultDataSourceFactory(PeertubeActivity.this,
Util.getUserAgent(PeertubeActivity.this, null), null);
}
ProgressiveMediaSource videoSource = null;
HlsMediaSource hlsMediaSource = null;
SingleSampleMediaSource subtitleSource = null;
DataSource.Factory dataSourceFactory = null;
if (video_cache == 0 || video.isLive()) {
dataSourceFactory = new DefaultDataSourceFactory(PeertubeActivity.this,
Util.getUserAgent(PeertubeActivity.this, null), null);
if (subtitles != null) {
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang);
subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
}
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.parse(videoURL)).build();
if (videoURL != null && !videoURL.endsWith("m3u8")) {
videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
} else {
hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItem);
}
} else {
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(PeertubeActivity.this);
MediaItem mediaItem = new MediaItem.Builder().setUri(videoURL).build();
if (subtitles != null) {
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang, Format.NO_VALUE);
subtitleSource = new SingleSampleMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
}
if (videoURL != null && !videoURL.endsWith("m3u8")) {
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem);
} else {
hlsMediaSource = new HlsMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(mediaItem);
}
if (subtitles != null) {
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang);
subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
}
if (lang != null && subtitleSource != null && videoSource != null) {
MergingMediaSource mergedSource =
new MergingMediaSource(videoSource, subtitleSource);
player.setMediaSource(mergedSource);
} else if (lang != null && subtitleSource != null) {
MergingMediaSource mergedSource =
new MergingMediaSource(hlsMediaSource, subtitleSource);
player.setMediaSource(mergedSource);
} else if (videoSource != null) {
player.setMediaSource(videoSource);
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.parse(videoURL)).build();
if (videoURL != null && !videoURL.endsWith("m3u8")) {
videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
} else {
player.setMediaSource(hlsMediaSource);
hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItem);
}
} else {
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(PeertubeActivity.this);
MediaItem mediaItem = new MediaItem.Builder().setUri(videoURL).build();
if (subtitles != null) {
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang, Format.NO_VALUE);
subtitleSource = new SingleSampleMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
}
if (videoURL != null && !videoURL.endsWith("m3u8")) {
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem);
} else {
hlsMediaSource = new HlsMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(mediaItem);
}
}
if (lang != null && subtitleSource != null && videoSource != null) {
MergingMediaSource mergedSource =
new MergingMediaSource(videoSource, subtitleSource);
player.setMediaSource(mergedSource);
} else if (lang != null && subtitleSource != null) {
MergingMediaSource mergedSource =
new MergingMediaSource(hlsMediaSource, subtitleSource);
player.setMediaSource(mergedSource);
} else if (videoSource != null) {
player.setMediaSource(videoSource);
} else {
player.setMediaSource(hlsMediaSource);
}
player.prepare();
if (position > 0) {
@ -1386,14 +1281,9 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
}
}
private void startStream(VideoData.Video video, String torrentLocal, String resolution, boolean autoPlay, long position, Uri subtitles, String lang, boolean promptNSFW) {
private void startStream(VideoData.Video video, String resolution, boolean autoPlay, long position, Uri subtitles, String lang, boolean promptNSFW) {
String videoURL;
if (torrentLocal != null) {
videoURL = torrentLocal;
} else {
videoURL = peertube.getFileUrl(resolution, PeertubeActivity.this);
}
String videoURL = peertube.getFileUrl(resolution, PeertubeActivity.this);
if (peertube != null && peertube.isLive() && videoURL == null) {
View parentLayout = findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make(parentLayout, R.string.live_not_started, Snackbar.LENGTH_INDEFINITE);
@ -1416,13 +1306,13 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
finish();
});
dialogBuilder.setPositiveButton(R.string.play, (dialog, id) -> {
stream(video, null, resolution, autoPlay, position, subtitles, lang);
stream(video, resolution, autoPlay, position, subtitles, lang);
dialog.dismiss();
});
alertDialog = dialogBuilder.create();
alertDialog.show();
} else {
stream(video, torrentLocal, resolution, autoPlay, position, subtitles, lang);
stream(video, resolution, autoPlay, position, subtitles, lang);
}
@ -1468,9 +1358,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
if (player != null) {
player.release();
}
if (torrentStream != null && torrentStream.isStreaming()) {
torrentStream.stopStream();
}
unregisterReceiver();
}
@ -1805,7 +1692,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
startStream(
peertube,
null,
res,
true, position, captionURI, captionLang, false);
}
@ -1849,7 +1735,6 @@ public class PeertubeActivity extends BasePeertubeActivity implements CommentLis
startStream(
peertube,
null,
null,
true,
newPosition,
captionURI,

View File

@ -348,15 +348,16 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
ListPreference set_video_mode_choice = findPreference(getString(R.string.set_video_mode_choice));
List<String> array = Arrays.asList(getResources().getStringArray(R.array.settings_video_mode));
CharSequence[] entries = array.toArray(new CharSequence[0]);
CharSequence[] entryValues = new CharSequence[4];
CharSequence[] entryValues = new CharSequence[2];
int video_mode = sharedpref.getInt(Helper.SET_VIDEO_MODE, Helper.VIDEO_MODE_NORMAL);
entryValues[0] = String.valueOf(Helper.VIDEO_MODE_NORMAL);
entryValues[1] = String.valueOf(Helper.VIDEO_MODE_MAGNET);
entryValues[2] = String.valueOf(Helper.VIDEO_MODE_WEBVIEW);
entryValues[3] = String.valueOf(Helper.VIDEO_MODE_TORRENT);
entryValues[1] = String.valueOf(Helper.VIDEO_MODE_WEBVIEW);
if (set_video_mode_choice != null) {
set_video_mode_choice.setEntries(entries);
set_video_mode_choice.setEntryValues(entryValues);
if(video_mode > Helper.VIDEO_MODE_WEBVIEW) {
video_mode = Helper.VIDEO_MODE_NORMAL;
}
set_video_mode_choice.setValueIndex(video_mode);
}

View File

@ -96,8 +96,8 @@ public class Helper {
public static final String DISPLAY = "display";
public static final String TIMELINE_TYPE = "timeline_type";
public static final int VIDEO_MODE_NORMAL = 0;
public static final int VIDEO_MODE_MAGNET = 2;
public static final int VIDEO_MODE_TORRENT = 3;
public static final int VIDEO_MODE_MAGNET = 0;
public static final int VIDEO_MODE_TORRENT = 0;
public static final int VIDEO_MODE_WEBVIEW = 1;
public static final int QUALITY_HIGH = 0;
public static final int QUALITY_MEDIUM = 1;

View File

@ -284,8 +284,6 @@
<string-array name="settings_video_mode">
<item>عادي</item>
<item>Webview</item>
<item>Magnet</item>
<item>تورنت</item>
</string-array>
<string-array name="settings_theme">
<item>فاتحة</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>WebView</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Hell</item>

View File

@ -279,8 +279,6 @@
<string-array name="settings_video_mode">
<item>Κανονική</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Φωτεινό</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Claro</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>Navigateur</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Clair</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normale</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Chiaro</item>

View File

@ -279,8 +279,6 @@
<string-array name="settings_video_mode">
<item>標準</item>
<item>WebView</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>ライト</item>

View File

@ -279,8 +279,6 @@
<string-array name="settings_video_mode">
<item>보통</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>밝음</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normaal</item>
<item>Webview</item>
<item>Magneetlink</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Licht</item>

View File

@ -282,8 +282,6 @@
<string-array name="settings_video_mode">
<item>Normalny</item>
<item>Webview</item>
<item>Magnes</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Jasny</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Diurno</item>

View File

@ -281,8 +281,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Light</item>

View File

@ -282,8 +282,6 @@
<string-array name="settings_video_mode">
<item>По умолчанию</item>
<item>Веб-просмотр</item>
<item>Magnet-ссылка</item>
<item>Торрент</item>
</string-array>
<string-array name="settings_theme">
<item>Светлая</item>

View File

@ -280,8 +280,6 @@
<string-array name="settings_video_mode">
<item>Nomal</item>
<item>Webbvy</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item>Ljust</item>

View File

@ -278,8 +278,6 @@
<string-array name="settings_video_mode">
<item>标准</item>
<item>网页播放器</item>
<item>Magnet</item>
<item>种子</item>
</string-array>
<string-array name="settings_theme">
<item>亮色</item>

View File

@ -279,8 +279,6 @@
<string-array name="settings_video_mode">
<item>正常</item>
<item>Torrent</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>
<string-array name="settings_theme">
<item></item>

View File

@ -350,8 +350,6 @@
<string-array name="settings_video_mode">
<item>Normal</item>
<item>Webview</item>
<item>Magnet</item>
<item>Torrent</item>
</string-array>

View File

@ -1,3 +1,2 @@
include ':torrentStream'
include ':app'
rootProject.name = "Fedilab Tube"

View File

@ -1 +0,0 @@
/build

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'de.undercouch.download'
group='com.github.TorrentStream'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 15
targetSdkVersion 30
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError true
}
}
ext {
libtorrentVersion = '1.2.11.0'
}
// Custom task which downloads the appropriate version of JAR files for jlibtorrent
task downloadDependencies(type: Download) {
def baseUrl = "https://github.com/frostwire/frostwire-jlibtorrent" +
"/releases/download/release%2F$libtorrentVersion"
def platforms = ['arm', 'arm64', 'x86', 'x86_64']
def urls = platforms.collect { "$baseUrl/jlibtorrent-android-$it-${libtorrentVersion}.jar" }
urls.add("$baseUrl/jlibtorrent-${libtorrentVersion}.jar")
src urls
dest 'libs'
overwrite false
}
// Add our custom task as a dependency to the build
// You may need to run gradle sync for IDE warnings to disappear
preBuild.dependsOn(downloadDependencies)
// Add deletion of libs folder to clean task
clean {
delete 'libs'
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation files('libs/jlibtorrent-1.2.11.0.jar')
}

View File

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Sebastiaan/Development/Android/SDK/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -1,3 +0,0 @@
<manifest package="com.github.se_bastiaan.torrentstream">
</manifest>

View File

@ -1,33 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream;
public class StreamStatus {
public final float progress;
public final int bufferProgress;
public final int seeds;
public final int peers;
public final int downloadSpeed;
StreamStatus(float progress, int bufferProgress, int seeds, int peers, int downloadSpeed) {
this.progress = progress;
this.bufferProgress = bufferProgress;
this.seeds = seeds;
this.peers = peers;
this.downloadSpeed = downloadSpeed;
}
}

View File

@ -1,530 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream;
import android.util.Log;
import com.frostwire.jlibtorrent.AlertListener;
import com.frostwire.jlibtorrent.FileStorage;
import com.frostwire.jlibtorrent.Priority;
import com.frostwire.jlibtorrent.TorrentFlags;
import com.frostwire.jlibtorrent.TorrentHandle;
import com.frostwire.jlibtorrent.TorrentInfo;
import com.frostwire.jlibtorrent.TorrentStatus;
import com.frostwire.jlibtorrent.alerts.Alert;
import com.frostwire.jlibtorrent.alerts.AlertType;
import com.frostwire.jlibtorrent.alerts.BlockFinishedAlert;
import com.frostwire.jlibtorrent.alerts.PieceFinishedAlert;
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class Torrent implements AlertListener {
private final static Integer MAX_PREPARE_COUNT = 20;
private final static Integer MIN_PREPARE_COUNT = 2;
private final static Integer DEFAULT_PREPARE_COUNT = 5;
private final static Integer SEQUENTIAL_CONCURRENT_PIECES_COUNT = 5;
private final List<WeakReference<TorrentInputStream>> torrentStreamReferences;
private final TorrentHandle torrentHandle;
private final TorrentListener listener;
private final Long prepareSize;
private Integer piecesToPrepare;
private Integer lastPieceIndex;
private Integer firstPieceIndex;
private Integer selectedFileIndex = -1;
private Integer interestedPieceIndex = 0;
private Double prepareProgress = 0d;
private Double progressStep = 0d;
private List<Integer> preparePieces;
private Boolean[] hasPieces;
private State state = State.RETRIEVING_META;
/**
* The constructor for a new Torrent
* <p/>
* First the largest file in the download is selected as the file for playback
* <p/>
* After setting this priority, the first and last index of the pieces that make up this file are determined.
* And last: amount of pieces that are needed for playback are calculated (needed for playback means: make up 10 megabyte of the file)
*
* @param torrentHandle jlibtorrent TorrentHandle
*/
public Torrent(TorrentHandle torrentHandle, TorrentListener listener, Long prepareSize) {
this.torrentHandle = torrentHandle;
this.listener = listener;
this.prepareSize = prepareSize;
torrentStreamReferences = new ArrayList<>();
if (selectedFileIndex == -1) {
setLargestFile();
}
if (this.listener != null) {
this.listener.onStreamPrepared(this);
}
}
/**
* Reset piece priorities of selected file to normal
*/
private void resetPriorities() {
Priority[] priorities = torrentHandle.piecePriorities();
for (int i = 0; i < priorities.length; i++) {
if (i >= firstPieceIndex && i <= lastPieceIndex) {
torrentHandle.piecePriority(i, Priority.NORMAL);
} else {
torrentHandle.piecePriority(i, Priority.IGNORE);
}
}
}
/**
* Get LibTorrent torrent handle of this torrent
*
* @return {@link TorrentHandle}
*/
public TorrentHandle getTorrentHandle() {
return torrentHandle;
}
public File getVideoFile() {
return new File(torrentHandle.savePath() + "/" + torrentHandle.torrentFile().files().filePath(selectedFileIndex));
}
/**
* Get an InputStream for the video file.
* Read is be blocked until the requested piece(s) is downloaded.
*
* @return {@link InputStream}
*/
public InputStream getVideoStream() throws FileNotFoundException {
File file = getVideoFile();
TorrentInputStream inputStream = new TorrentInputStream(this, new FileInputStream(file));
torrentStreamReferences.add(new WeakReference<>(inputStream));
return inputStream;
}
/**
* Get the location of the file that is being downloaded
*
* @return {@link File} The file location
*/
public File getSaveLocation() {
return new File(torrentHandle.savePath() + "/" + torrentHandle.name());
}
/**
* Resume the torrent download
*/
public void resume() {
torrentHandle.resume();
}
/**
* Pause the torrent download
*/
public void pause() {
torrentHandle.pause();
}
/**
* Set the selected file index to the largest file in the torrent
*/
public void setLargestFile() {
setSelectedFileIndex(-1);
}
/**
* Set the index of the file that should be downloaded
* If the given index is -1, then the largest file is chosen
*
* @param selectedFileIndex {@link Integer} Index of the file
*/
public void setSelectedFileIndex(Integer selectedFileIndex) {
TorrentInfo torrentInfo = torrentHandle.torrentFile();
FileStorage fileStorage = torrentInfo.files();
if (selectedFileIndex == -1) {
long highestFileSize = 0;
int selectedFile = -1;
for (int i = 0; i < fileStorage.numFiles(); i++) {
long fileSize = fileStorage.fileSize(i);
if (highestFileSize < fileSize) {
highestFileSize = fileSize;
torrentHandle.filePriority(selectedFile, Priority.IGNORE);
selectedFile = i;
torrentHandle.filePriority(i, Priority.NORMAL);
} else {
torrentHandle.filePriority(i, Priority.IGNORE);
}
}
selectedFileIndex = selectedFile;
} else {
for (int i = 0; i < fileStorage.numFiles(); i++) {
if (i == selectedFileIndex) {
torrentHandle.filePriority(i, Priority.NORMAL);
} else {
torrentHandle.filePriority(i, Priority.IGNORE);
}
}
}
this.selectedFileIndex = selectedFileIndex;
Priority[] piecePriorities = torrentHandle.piecePriorities();
int firstPieceIndexLocal = -1;
int lastPieceIndexLocal = -1;
for (int i = 0; i < piecePriorities.length; i++) {
if (piecePriorities[i] != Priority.IGNORE) {
if (firstPieceIndexLocal == -1) {
firstPieceIndexLocal = i;
}
piecePriorities[i] = Priority.IGNORE;
} else {
if (firstPieceIndexLocal != -1 && lastPieceIndexLocal == -1) {
lastPieceIndexLocal = i - 1;
}
}
}
if (lastPieceIndexLocal == -1) {
lastPieceIndexLocal = piecePriorities.length - 1;
}
int pieceCount = lastPieceIndexLocal - firstPieceIndexLocal + 1;
int pieceLength = torrentHandle.torrentFile().pieceLength();
int activePieceCount;
if (pieceLength > 0) {
activePieceCount = (int) (prepareSize / pieceLength);
if (activePieceCount < MIN_PREPARE_COUNT) {
activePieceCount = MIN_PREPARE_COUNT;
} else if (activePieceCount > MAX_PREPARE_COUNT) {
activePieceCount = MAX_PREPARE_COUNT;
}
} else {
activePieceCount = DEFAULT_PREPARE_COUNT;
}
if (pieceCount < activePieceCount) {
activePieceCount = pieceCount / 2;
}
this.firstPieceIndex = firstPieceIndexLocal;
this.interestedPieceIndex = this.firstPieceIndex;
this.lastPieceIndex = lastPieceIndexLocal;
piecesToPrepare = activePieceCount;
}
/**
* Get the filenames of the files in the torrent
*
* @return {@link String[]}
*/
public String[] getFileNames() {
FileStorage fileStorage = torrentHandle.torrentFile().files();
String[] fileNames = new String[fileStorage.numFiles()];
for (int i = 0; i < fileStorage.numFiles(); i++) {
fileNames[i] = fileStorage.fileName(i);
}
return fileNames;
}
/**
* Prepare torrent for playback. Prioritize the first {@code piecesToPrepare} pieces and the last {@code piecesToPrepare} pieces
* from {@code firstPieceIndex} and {@code lastPieceIndex}. Ignore all other pieces.
*/
public void startDownload() {
Log.v("TubeLab","startDownload: " + state);
if (state == State.STREAMING || state == State.STARTING) return;
state = State.STARTING;
List<Integer> indices = new ArrayList<>();
Priority[] priorities = torrentHandle.piecePriorities();
for (int i = 0; i < priorities.length; i++) {
if (priorities[i] != Priority.IGNORE) {
torrentHandle.piecePriority(i, Priority.NORMAL);
}
}
for (int i = 0; i < piecesToPrepare; i++) {
indices.add(lastPieceIndex - i);
torrentHandle.piecePriority(lastPieceIndex - i, Priority.SEVEN);
torrentHandle.setPieceDeadline(lastPieceIndex - i, 1000);
}
for (int i = 0; i < piecesToPrepare; i++) {
indices.add(firstPieceIndex + i);
torrentHandle.piecePriority(firstPieceIndex + i, Priority.SEVEN);
torrentHandle.setPieceDeadline(firstPieceIndex + i, 1000);
}
preparePieces = indices;
hasPieces = new Boolean[lastPieceIndex - firstPieceIndex + 1];
Arrays.fill(hasPieces, false);
TorrentInfo torrentInfo = torrentHandle.torrentFile();
TorrentStatus status = torrentHandle.status();
double blockCount = indices.size() * torrentInfo.pieceLength() / status.blockSize();
progressStep = 100 / blockCount;
torrentStreamReferences.clear();
torrentHandle.resume();
Log.v("TubeLab","onStreamStarted: " );
listener.onStreamStarted(this);
}
/**
* Check if the piece that contains the specified bytes were downloaded already
*
* @param bytes The bytes you're interested in
* @return {@code true} if downloaded, {@code false} if not
*/
public boolean hasBytes(long bytes) {
if (hasPieces == null) {
return false;
}
int pieceIndex = (int) (bytes / torrentHandle.torrentFile().pieceLength());
return hasPieces[pieceIndex];
}
/**
* Set the bytes of the selected file that you're interested in
* The piece of that specific offset is selected and that piece plus the 1 preceding and the 3 after it.
* These pieces will then be prioritised, which results in continuing the sequential download after that piece
*
* @param bytes The bytes you're interested in
*/
public void setInterestedBytes(long bytes) {
if (hasPieces == null && bytes >= 0) {
return;
}
int pieceIndex = (int) (bytes / torrentHandle.torrentFile().pieceLength());
interestedPieceIndex = pieceIndex;
if (!hasPieces[pieceIndex] && torrentHandle.piecePriority(pieceIndex + firstPieceIndex) != Priority.SEVEN) {
interestedPieceIndex = pieceIndex;
int pieces = 5;
for (int i = pieceIndex; i < hasPieces.length; i++) {
// Set full priority to first found piece that is not confirmed finished
if (!hasPieces[i]) {
torrentHandle.piecePriority(i + firstPieceIndex, Priority.SEVEN);
torrentHandle.setPieceDeadline(i + firstPieceIndex, 1000);
pieces--;
if (pieces == 0) {
break;
}
}
}
}
}
/**
* Checks if the interesting pieces are downloaded already
*
* @return {@code true} if the 5 pieces that were selected using `setInterestedBytes` are all reported complete including the `nextPieces`, {@code false} if not
*/
public boolean hasInterestedBytes(int nextPieces) {
for (int i = 0; i < 5 + nextPieces; i++) {
int index = interestedPieceIndex + i;
if (hasPieces.length <= index || index < 0) {
continue;
}
if (!hasPieces[interestedPieceIndex + i]) {
return false;
}
}
return true;
}
/**
* Checks if the interesting pieces are downloaded already
*
* @return {@code true} if the 5 pieces that were selected using `setInterestedBytes` are all reported complete, {@code false} if not
*/
public boolean hasInterestedBytes() {
return hasInterestedBytes(5);
}
/**
* Get the index of the piece we're currently interested in
*
* @return Interested piece index
*/
public int getInterestedPieceIndex() {
return interestedPieceIndex;
}
/**
* Get amount of pieces to prepare
*
* @return Amount of pieces to prepare
*/
public Integer getPiecesToPrepare() {
return piecesToPrepare;
}
/**
* Start sequential mode downloading
*/
private void startSequentialMode() {
resetPriorities();
if (hasPieces == null) {
torrentHandle.setFlags(torrentHandle.flags().and_(TorrentFlags.SEQUENTIAL_DOWNLOAD));
} else {
for (int i = firstPieceIndex + piecesToPrepare; i < firstPieceIndex + piecesToPrepare + SEQUENTIAL_CONCURRENT_PIECES_COUNT; i++) {
torrentHandle.piecePriority(i, Priority.SEVEN);
torrentHandle.setPieceDeadline(i, 1000);
}
}
}
/**
* Get current torrent state
*
* @return {@link State}
*/
public State getState() {
return state;
}
/**
* Piece finished
*
* @param alert
*/
private void pieceFinished(PieceFinishedAlert alert) {
if (state == State.STREAMING && hasPieces != null) {
int pieceIndex = alert.pieceIndex() - firstPieceIndex;
hasPieces[pieceIndex] = true;
if (pieceIndex >= interestedPieceIndex) {
for (int i = pieceIndex; i < hasPieces.length; i++) {
// Set full priority to first found piece that is not confirmed finished
if (!hasPieces[i]) {
torrentHandle.piecePriority(i + firstPieceIndex, Priority.SEVEN);
torrentHandle.setPieceDeadline(i + firstPieceIndex, 1000);
break;
}
}
}
} else {
Iterator<Integer> piecesIterator = preparePieces.iterator();
while (piecesIterator.hasNext()) {
int index = piecesIterator.next();
if (index == alert.pieceIndex()) {
piecesIterator.remove();
}
}
if (hasPieces != null) {
hasPieces[alert.pieceIndex() - firstPieceIndex] = true;
}
if (preparePieces.size() == 0) {
startSequentialMode();
prepareProgress = 100d;
sendStreamProgress();
state = State.STREAMING;
if (listener != null) {
listener.onStreamReady(this);
}
}
}
}
private void blockFinished(BlockFinishedAlert alert) {
for (Integer index : preparePieces) {
if (index == alert.pieceIndex()) {
prepareProgress += progressStep;
break;
}
}
sendStreamProgress();
}
private void sendStreamProgress() {
TorrentStatus status = torrentHandle.status();
float progress = status.progress() * 100;
int seeds = status.numSeeds();
int peers = status.numPeers();
int downloadSpeed = status.downloadPayloadRate();
if (listener != null && prepareProgress >= 1) {
listener.onStreamProgress(this, new StreamStatus(progress, prepareProgress.intValue(), seeds, peers, downloadSpeed));
}
}
@Override
public int[] types() {
return new int[]{
AlertType.PIECE_FINISHED.swig(),
AlertType.BLOCK_FINISHED.swig()
};
}
@Override
public void alert(Alert<?> alert) {
switch (alert.type()) {
case PIECE_FINISHED:
pieceFinished((PieceFinishedAlert) alert);
break;
case BLOCK_FINISHED:
blockFinished((BlockFinishedAlert) alert);
break;
default:
break;
}
Iterator<WeakReference<TorrentInputStream>> i = torrentStreamReferences.iterator();
while (i.hasNext()) {
WeakReference<TorrentInputStream> reference = i.next();
TorrentInputStream inputStream = reference.get();
if (inputStream == null) {
i.remove();
continue;
}
inputStream.alert(alert);
}
}
public enum State {UNKNOWN, RETRIEVING_META, STARTING, STREAMING}
}

View File

@ -1,116 +0,0 @@
package com.github.se_bastiaan.torrentstream;
import com.frostwire.jlibtorrent.AlertListener;
import com.frostwire.jlibtorrent.alerts.Alert;
import com.frostwire.jlibtorrent.alerts.AlertType;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
class TorrentInputStream extends FilterInputStream implements AlertListener {
private final Torrent torrent;
private boolean stopped;
private long location;
TorrentInputStream(Torrent torrent, InputStream inputStream) {
super(inputStream);
this.torrent = torrent;
}
@Override
protected void finalize() throws Throwable {
synchronized (this) {
stopped = true;
notifyAll();
}
super.finalize();
}
private synchronized boolean waitForPiece(long offset) {
while (!Thread.currentThread().isInterrupted() && !stopped) {
try {
if (torrent.hasBytes(offset)) {
return true;
}
wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
return false;
}
@Override
public synchronized int read() throws IOException {
if (!waitForPiece(location)) {
return -1;
}
location++;
return super.read();
}
@Override
public synchronized int read(byte[] buffer, int offset, int length) throws IOException {
int pieceLength = torrent.getTorrentHandle().torrentFile().pieceLength();
for (int i = 0; i < length; i += pieceLength) {
if (!waitForPiece(location + i)) {
return -1;
}
}
location += length;
return super.read(buffer, offset, length);
}
@Override
public void close() throws IOException {
synchronized (this) {
stopped = true;
notifyAll();
}
super.close();
}
@Override
public synchronized long skip(long n) throws IOException {
location += n;
return super.skip(n);
}
@Override
public boolean markSupported() {
return false;
}
private synchronized void pieceFinished() {
notifyAll();
}
@Override
public int[] types() {
return new int[]{
AlertType.PIECE_FINISHED.swig(),
};
}
@Override
public void alert(Alert<?> alert) {
switch (alert.type()) {
case PIECE_FINISHED:
pieceFinished();
break;
default:
break;
}
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream;
import java.io.File;
public final class TorrentOptions {
String saveLocation = "/";
String proxyHost;
String proxyUsername;
String proxyPassword;
String peerFingerprint;
Integer maxDownloadSpeed = 0;
Integer maxUploadSpeed = 0;
Integer maxConnections = 200;
Integer maxDht = 88;
Integer listeningPort = -1;
Boolean removeFiles = false;
Boolean anonymousMode = false;
Boolean autoDownload = true;
Long prepareSize = 15 * 1024L * 1024L;
private TorrentOptions() {
// Unused
}
private TorrentOptions(TorrentOptions torrentOptions) {
this.saveLocation = torrentOptions.saveLocation;
this.proxyHost = torrentOptions.proxyHost;
this.proxyUsername = torrentOptions.proxyUsername;
this.proxyPassword = torrentOptions.proxyPassword;
this.peerFingerprint = torrentOptions.peerFingerprint;
this.maxDownloadSpeed = torrentOptions.maxDownloadSpeed;
this.maxUploadSpeed = torrentOptions.maxUploadSpeed;
this.maxConnections = torrentOptions.maxConnections;
this.maxDht = torrentOptions.maxDht;
this.listeningPort = torrentOptions.listeningPort;
this.removeFiles = torrentOptions.removeFiles;
this.anonymousMode = torrentOptions.anonymousMode;
this.autoDownload = torrentOptions.autoDownload;
this.prepareSize = torrentOptions.prepareSize;
}
public Builder toBuilder() {
return new Builder(this);
}
public static class Builder {
private TorrentOptions torrentOptions;
public Builder() {
torrentOptions = new TorrentOptions();
}
private Builder(TorrentOptions torrentOptions) {
torrentOptions = new TorrentOptions(torrentOptions);
}
public Builder saveLocation(String saveLocation) {
torrentOptions.saveLocation = saveLocation;
return this;
}
public Builder saveLocation(File saveLocation) {
torrentOptions.saveLocation = saveLocation.getAbsolutePath();
return this;
}
public Builder maxUploadSpeed(Integer maxUploadSpeed) {
torrentOptions.maxUploadSpeed = maxUploadSpeed;
return this;
}
public Builder maxDownloadSpeed(Integer maxDownloadSpeed) {
torrentOptions.maxDownloadSpeed = maxDownloadSpeed;
return this;
}
public Builder maxConnections(Integer maxConnections) {
torrentOptions.maxConnections = maxConnections;
return this;
}
public Builder maxActiveDHT(Integer maxActiveDHT) {
torrentOptions.maxDht = maxActiveDHT;
return this;
}
public Builder removeFilesAfterStop(Boolean b) {
torrentOptions.removeFiles = b;
return this;
}
public Builder prepareSize(Long prepareSize) {
torrentOptions.prepareSize = prepareSize;
return this;
}
public Builder listeningPort(Integer port) {
torrentOptions.listeningPort = port;
return this;
}
public Builder proxy(String host, String username, String password) {
torrentOptions.proxyHost = host;
torrentOptions.proxyUsername = username;
torrentOptions.proxyPassword = password;
return this;
}
public Builder peerFingerprint(String peerId) {
torrentOptions.peerFingerprint = peerId;
torrentOptions.anonymousMode = false;
return this;
}
public Builder anonymousMode(Boolean enable) {
torrentOptions.anonymousMode = enable;
if (torrentOptions.anonymousMode)
torrentOptions.peerFingerprint = null;
return this;
}
public Builder autoDownload(Boolean enable) {
torrentOptions.autoDownload = enable;
return this;
}
public TorrentOptions build() {
return torrentOptions;
}
}
}

View File

@ -1,542 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import com.frostwire.jlibtorrent.Priority;
import com.frostwire.jlibtorrent.SessionManager;
import com.frostwire.jlibtorrent.SessionParams;
import com.frostwire.jlibtorrent.SettingsPack;
import com.frostwire.jlibtorrent.TorrentHandle;
import com.frostwire.jlibtorrent.TorrentInfo;
import com.frostwire.jlibtorrent.alerts.AddTorrentAlert;
import com.frostwire.jlibtorrent.swig.settings_pack;
import com.github.se_bastiaan.torrentstream.exceptions.DirectoryModifyException;
import com.github.se_bastiaan.torrentstream.exceptions.NotInitializedException;
import com.github.se_bastiaan.torrentstream.exceptions.TorrentInfoException;
import com.github.se_bastiaan.torrentstream.listeners.DHTStatsAlertListener;
import com.github.se_bastiaan.torrentstream.listeners.TorrentAddedAlertListener;
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
import com.github.se_bastiaan.torrentstream.utils.FileUtils;
import com.github.se_bastiaan.torrentstream.utils.ThreadUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
public final class TorrentStream {
private static final String LIBTORRENT_THREAD_NAME = "TORRENTSTREAM_LIBTORRENT", STREAMING_THREAD_NAME = "TORRENTSTREAMER_STREAMING";
private static TorrentStream sThis;
private final List<TorrentListener> listeners = new ArrayList<>();
private CountDownLatch initialisingLatch;
private SessionManager torrentSession;
private Boolean initialising = false, initialised = false, isStreaming = false, isCanceled = false;
private TorrentOptions torrentOptions;
private Torrent currentTorrent;
private final TorrentAddedAlertListener torrentAddedAlertListener = new TorrentAddedAlertListener() {
@Override
public void torrentAdded(AddTorrentAlert alert) {
InternalTorrentListener listener = new InternalTorrentListener();
TorrentHandle th = torrentSession.find(alert.handle().infoHash());
currentTorrent = new Torrent(th, listener, torrentOptions.prepareSize);
torrentSession.addListener(currentTorrent);
}
};
private String currentTorrentUrl;
private Integer dhtNodes = 0;
private final DHTStatsAlertListener dhtStatsAlertListener = new DHTStatsAlertListener() {
@Override
public void stats(int totalDhtNodes) {
dhtNodes = totalDhtNodes;
}
};
private HandlerThread libTorrentThread, streamingThread;
private Handler libTorrentHandler, streamingHandler;
private TorrentStream(TorrentOptions options) {
torrentOptions = options;
initialise();
}
public static TorrentStream init(TorrentOptions options) {
sThis = new TorrentStream(options);
return sThis;
}
public static TorrentStream getInstance() throws NotInitializedException {
if (sThis == null)
throw new NotInitializedException();
return sThis;
}
/**
* Obtain internal session manager
*
* @return {@link SessionManager}
*/
public SessionManager getSessionManager() {
return torrentSession;
}
private void initialise() {
if (libTorrentThread != null && torrentSession != null) {
resumeSession();
} else {
if ((initialising || initialised) && libTorrentThread != null) {
libTorrentThread.interrupt();
}
initialising = true;
initialised = false;
initialisingLatch = new CountDownLatch(1);
libTorrentThread = new HandlerThread(LIBTORRENT_THREAD_NAME);
libTorrentThread.start();
libTorrentHandler = new Handler(libTorrentThread.getLooper());
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession = new SessionManager();
setOptions(torrentOptions);
torrentSession.addListener(dhtStatsAlertListener);
torrentSession.startDht();
initialising = false;
initialised = true;
initialisingLatch.countDown();
}
});
}
}
/**
* Resume TorrentSession
*/
public void resumeSession() {
if (libTorrentThread != null && torrentSession != null) {
libTorrentHandler.removeCallbacksAndMessages(null);
//resume torrent session if needed
if (torrentSession.isPaused()) {
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.resume();
}
});
}
if (!torrentSession.isDhtRunning()) {
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.startDht();
}
});
}
}
}
/**
* Pause TorrentSession
*/
public void pauseSession() {
if (!isStreaming)
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.pause();
}
});
}
/**
* Get torrent metadata, either by downloading the .torrent or fetching the magnet
*
* @param torrentUrl {@link String} URL to .torrent or magnet link
* @return {@link TorrentInfo}
*/
private TorrentInfo getTorrentInfo(String torrentUrl) throws TorrentInfoException {
if (torrentUrl.startsWith("magnet")) {
byte[] data = torrentSession.fetchMagnet(torrentUrl, 30);
if (data != null)
try {
return TorrentInfo.bdecode(data);
} catch (IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
} else if (torrentUrl.startsWith("http") || torrentUrl.startsWith("https")) {
try {
URL url = new URL(torrentUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setInstanceFollowRedirects(true);
connection.connect();
InputStream inputStream = connection.getInputStream();
byte[] responseByteArray = new byte[0];
if (connection.getResponseCode() == 200) {
responseByteArray = getBytesFromInputStream(inputStream);
}
inputStream.close();
connection.disconnect();
if (responseByteArray.length > 0) {
return TorrentInfo.bdecode(responseByteArray);
}
} catch (IOException | IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
} else if (torrentUrl.startsWith("file")) {
Uri path = Uri.parse(torrentUrl);
File file = new File(path.getPath());
try {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] responseByteArray = getBytesFromInputStream(fileInputStream);
fileInputStream.close();
if (responseByteArray.length > 0) {
return TorrentInfo.bdecode(responseByteArray);
}
} catch (IOException | IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
}
return null;
}
private byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
}
/**
* Start stream download for specified torrent
*
* @param torrentUrl {@link String} .torrent or magnet link
*/
public void startStream(final String torrentUrl) {
if (!initialising && !initialised)
initialise();
if (libTorrentHandler == null || isStreaming) return;
isCanceled = false;
streamingThread = new HandlerThread(STREAMING_THREAD_NAME);
streamingThread.start();
streamingHandler = new Handler(streamingThread.getLooper());
streamingHandler.post(new Runnable() {
@Override
public void run() {
isStreaming = true;
if (initialisingLatch != null) {
try {
initialisingLatch.await();
initialisingLatch = null;
} catch (InterruptedException e) {
isStreaming = false;
return;
}
}
currentTorrentUrl = torrentUrl;
File saveDirectory = new File(torrentOptions.saveLocation);
if (!saveDirectory.isDirectory() && !saveDirectory.mkdirs()) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, new DirectoryModifyException());
}
});
}
isStreaming = false;
return;
}
torrentSession.removeListener(torrentAddedAlertListener);
TorrentInfo torrentInfo = null;
try {
torrentInfo = getTorrentInfo(torrentUrl);
} catch (final TorrentInfoException e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, e);
}
});
}
}
torrentSession.addListener(torrentAddedAlertListener);
if (torrentInfo == null) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, new TorrentInfoException(null));
}
});
}
isStreaming = false;
return;
}
Priority[] priorities = new Priority[torrentInfo.numFiles()];
for (int i = 0; i < priorities.length; i++) {
priorities[i] = Priority.IGNORE;
}
if (!currentTorrentUrl.equals(torrentUrl) || isCanceled) {
return;
}
torrentSession.download(torrentInfo, saveDirectory, null, priorities, null);
}
});
}
/**
* Stop current torrent stream
*/
public void stopStream() {
//remove all callbacks from handler
if (libTorrentHandler != null)
libTorrentHandler.removeCallbacksAndMessages(null);
if (streamingHandler != null)
streamingHandler.removeCallbacksAndMessages(null);
isCanceled = true;
isStreaming = false;
if (currentTorrent != null) {
final File saveLocation = currentTorrent.getSaveLocation();
currentTorrent.pause();
torrentSession.removeListener(currentTorrent);
torrentSession.remove(currentTorrent.getTorrentHandle());
currentTorrent = null;
if (torrentOptions.removeFiles) {
new Thread(new Runnable() {
@Override
public void run() {
int tries = 0;
while (!FileUtils.recursiveDelete(saveLocation) && tries < 5) {
tries++;
try {
Thread.sleep(1000); // If deleted failed then something is still using the file, wait and then retry
} catch (InterruptedException e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(currentTorrent, new DirectoryModifyException());
}
});
}
}
}
}
}).start();
}
}
if (streamingThread != null)
streamingThread.interrupt();
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamStopped();
}
});
}
}
public TorrentOptions getOptions() {
return torrentOptions;
}
public void setOptions(TorrentOptions options) {
torrentOptions = options;
SettingsPack settingsPack = new SettingsPack()
.anonymousMode(torrentOptions.anonymousMode)
.connectionsLimit(torrentOptions.maxConnections)
.downloadRateLimit(torrentOptions.maxDownloadSpeed)
.uploadRateLimit(torrentOptions.maxUploadSpeed)
.activeDhtLimit(torrentOptions.maxDht);
if (torrentOptions.listeningPort != -1) {
String ifStr = String.format(Locale.ENGLISH, "%s:%d", "0.0.0.0", torrentOptions.listeningPort);
settingsPack.setString(settings_pack.string_types.listen_interfaces.swigValue(), ifStr);
}
if (torrentOptions.proxyHost != null) {
settingsPack.setString(settings_pack.string_types.proxy_hostname.swigValue(), torrentOptions.proxyHost);
if (torrentOptions.proxyUsername != null) {
settingsPack.setString(settings_pack.string_types.proxy_username.swigValue(), torrentOptions.proxyUsername);
if (torrentOptions.proxyPassword != null) {
settingsPack.setString(settings_pack.string_types.proxy_password.swigValue(), torrentOptions.proxyPassword);
}
}
}
if (torrentOptions.peerFingerprint != null) {
settingsPack.setString(settings_pack.string_types.peer_fingerprint.swigValue(), torrentOptions.peerFingerprint);
}
if (!torrentSession.isRunning()) {
SessionParams sessionParams = new SessionParams(settingsPack);
torrentSession.start(sessionParams);
} else {
torrentSession.applySettings(settingsPack);
}
}
public boolean isStreaming() {
return isStreaming;
}
public String getCurrentTorrentUrl() {
return currentTorrentUrl;
}
public Integer getTotalDhtNodes() {
return dhtNodes;
}
public Torrent getCurrentTorrent() {
return currentTorrent;
}
public void addListener(TorrentListener listener) {
if (listener != null)
listeners.add(listener);
}
public void removeListener(TorrentListener listener) {
if (listener != null)
listeners.remove(listener);
}
protected class InternalTorrentListener implements TorrentListener {
public void onStreamStarted(final Torrent torrent) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamStarted(torrent);
}
});
}
}
public void onStreamError(final Torrent torrent, final Exception e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(torrent, e);
}
});
}
}
public void onStreamReady(final Torrent torrent) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamReady(torrent);
}
});
}
}
public void onStreamProgress(final Torrent torrent, final StreamStatus status) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamProgress(torrent, status);
}
});
}
}
@Override
public void onStreamStopped() {
// Not used
}
@Override
public void onStreamPrepared(final Torrent torrent) {
if (torrentOptions.autoDownload) {
torrent.startDownload();
}
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamPrepared(torrent);
}
});
}
}
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream.exceptions;
public class DirectoryModifyException extends Exception {
public DirectoryModifyException() {
super("Could not create or delete save directory");
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream.exceptions;
public class NotInitializedException extends Exception {
public NotInitializedException() {
super("TorrentStreamer is not initialized. Call init() first before getting an instance.");
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream.exceptions;
public class TorrentInfoException extends Exception {
public TorrentInfoException(Throwable cause) {
super("No torrent info could be found or read", cause);
}
}

View File

@ -1,57 +0,0 @@
/*
*
* * This file is part of TorrentStreamer-Android.
* *
* * TorrentStreamer-Android is free software: you can redistribute it and/or modify
* * it under the terms of the GNU Lesser General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * TorrentStreamer-Android 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 Lesser General Public License for more details.
* *
* * You should have received a copy of the GNU Lesser General Public License
* * along with TorrentStreamer-Android. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.github.se_bastiaan.torrentstream.listeners;
import com.frostwire.jlibtorrent.AlertListener;
import com.frostwire.jlibtorrent.DhtRoutingBucket;
import com.frostwire.jlibtorrent.alerts.Alert;
import com.frostwire.jlibtorrent.alerts.AlertType;
import com.frostwire.jlibtorrent.alerts.DhtStatsAlert;
import java.util.ArrayList;
public abstract class DHTStatsAlertListener implements AlertListener {
@Override
public int[] types() {
return new int[]{AlertType.DHT_STATS.swig()};
}
public void alert(Alert<?> alert) {
if (alert instanceof DhtStatsAlert) {
DhtStatsAlert dhtAlert = (DhtStatsAlert) alert;
stats(countTotalDHTNodes(dhtAlert));
}
}
public abstract void stats(int totalDhtNodes);
private int countTotalDHTNodes(DhtStatsAlert alert) {
final ArrayList<DhtRoutingBucket> routingTable = alert.routingTable();
int totalNodes = 0;
if (routingTable != null) {
for (DhtRoutingBucket bucket : routingTable) {
totalNodes += bucket.numNodes();
}
}
return totalNodes;
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream.listeners;
import com.frostwire.jlibtorrent.AlertListener;
import com.frostwire.jlibtorrent.alerts.AddTorrentAlert;
import com.frostwire.jlibtorrent.alerts.Alert;
import com.frostwire.jlibtorrent.alerts.AlertType;
public abstract class TorrentAddedAlertListener implements AlertListener {
@Override
public int[] types() {
return new int[]{AlertType.ADD_TORRENT.swig()};
}
@Override
public void alert(Alert<?> alert) {
switch (alert.type()) {
case ADD_TORRENT:
torrentAdded((AddTorrentAlert) alert);
break;
default:
break;
}
}
public abstract void torrentAdded(AddTorrentAlert alert);
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream.listeners;
import com.github.se_bastiaan.torrentstream.StreamStatus;
import com.github.se_bastiaan.torrentstream.Torrent;
public interface TorrentListener {
void onStreamPrepared(Torrent torrent);
void onStreamStarted(Torrent torrent);
void onStreamError(Torrent torrent, Exception e);
void onStreamReady(Torrent torrent);
void onStreamProgress(Torrent torrent, StreamStatus status);
void onStreamStopped();
}

View File

@ -1,48 +0,0 @@
/*
*
* * This file is part of TorrentStreamer-Android.
* *
* * TorrentStreamer-Android is free software: you can redistribute it and/or modify
* * it under the terms of the GNU Lesser General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * TorrentStreamer-Android 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 Lesser General Public License for more details.
* *
* * You should have received a copy of the GNU Lesser General Public License
* * along with TorrentStreamer-Android. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.github.se_bastiaan.torrentstream.utils;
import java.io.File;
public final class FileUtils {
private FileUtils() throws InstantiationException {
throw new InstantiationException("This class is not created for instantiation");
}
/**
* Delete every item below the File location
*
* @param file Location
* @return {@code true} when successful delete
*/
public static boolean recursiveDelete(File file) {
if (file.isDirectory()) {
String[] children = file.list();
if (children == null) return false;
for (String child : children) {
recursiveDelete(new File(file, child));
}
}
return file.delete();
}
}

View File

@ -1,42 +0,0 @@
/*
*
* * This file is part of TorrentStreamer-Android.
* *
* * TorrentStreamer-Android is free software: you can redistribute it and/or modify
* * it under the terms of the GNU Lesser General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * TorrentStreamer-Android 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 Lesser General Public License for more details.
* *
* * You should have received a copy of the GNU Lesser General Public License
* * along with TorrentStreamer-Android. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.github.se_bastiaan.torrentstream.utils;
import android.os.Handler;
import android.os.Looper;
public final class ThreadUtils {
private ThreadUtils() throws InstantiationException {
throw new InstantiationException("This class is not created for instantiation");
}
/**
* Execute the given {@link Runnable} on the ui thread.
*
* @param runnable The runnable to execute.
*/
public static void runOnUiThread(Runnable runnable) {
Thread uiThread = Looper.getMainLooper().getThread();
if (Thread.currentThread() != uiThread) new Handler(Looper.getMainLooper()).post(runnable);
else runnable.run();
}
}