diff --git a/README.md b/README.md index 8659503..16e2a42 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@

- screenshot + screenshot + screenshot

## Features @@ -15,6 +16,7 @@ - [X] Trending Videos - [X] Endless scrolling - [X] Pull to refresh +- [X] Very Basic Torrent playback - [ ] Video Playback via WebRTC - [ ] Pick Server - [ ] Login diff --git a/Screenshot.png b/Screenshot.png deleted file mode 100644 index 3b93c99..0000000 Binary files a/Screenshot.png and /dev/null differ diff --git a/Screenshot1.png b/Screenshot1.png new file mode 100644 index 0000000..a962100 Binary files /dev/null and b/Screenshot1.png differ diff --git a/Screenshot2.png b/Screenshot2.png new file mode 100644 index 0000000..180116c Binary files /dev/null and b/Screenshot2.png differ diff --git a/app/build.gradle b/app/build.gradle index 7ae9a98..1162fec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,11 +38,15 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.2' - implementation 'org.webrtc:google-webrtc:1.0.+' +// implementation 'org.webrtc:google-webrtc:1.0.+' implementation 'com.android.support:design:27.1.0' implementation 'com.blackboardtheory:android-iconify-fontawesome:3.0.1-SNAPSHOT' + implementation 'com.github.TorrentStream:TorrentStream-Android:2.5.0' + implementation 'com.google.android.exoplayer:exoplayer:2.7.0' +// implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1" +// implementation 'com.devbrackets.android:exomedia:4.1.0' implementation 'com.android.support:support-v4:27.1.0' testImplementation 'junit:junit:4.12' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 825e7d4..dc7caf1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + + android:label="@string/title_activity_login" /> + android:name=".activity.TorrentVideoPlayActivity" + android:label="@string/title_activity_torrent_video_play" + android:theme="@style/AppTheme" /> + \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java b/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..1ea9f48 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java @@ -0,0 +1,109 @@ +package net.schueller.peertube.activity; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java new file mode 100644 index 0000000..c35773f --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java @@ -0,0 +1,152 @@ +package net.schueller.peertube.activity; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; + +import android.os.Build; +import android.os.Bundle; +import android.preference.Preference; +import android.support.v7.app.ActionBar; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; + +import android.view.MenuItem; + + +import net.schueller.peertube.R; + +import java.util.List; + + +public class SettingsActivity extends AppCompatPreferenceActivity { + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + this.finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * A preference value change listener that updates the preference's summary + * to reflect its new value. + */ + private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> { + String stringValue = value.toString(); + + preference.setSummary(stringValue); + + return true; + }; + + /** + * Helper method to determine if the device has an extra-large screen. For + * example, 10" tablets are extra-large. + */ + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + /** + * Binds a preference's summary to its value. More specifically, when the + * preference's value is changed, its summary (line of text below the + * preference title) is updated to reflect the value. The summary is also + * immediately updated upon calling this method. The exact display format is + * dependent on the type of preference. + * + * @see #sBindPreferenceSummaryToValueListener + */ + private static void bindPreferenceSummaryToValue(Preference preference) { + // Set the listener to watch for value changes. + preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); + + // Trigger the listener immediately with the preference's + // current value. + sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + getFragmentManager().beginTransaction().replace(android.R.id.content, new GeneralPreferenceFragment()).commit(); + + } + + /** + * Set up the {@link android.app.ActionBar}, if the API is available. + */ + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + // Show the Up button in the action bar. + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + /** + * {@inheritDoc} + */ + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List
target) { + //loadHeadersFromResource(R.xml.pref_headers, target); + } + + /** + * This method stops fragment injection in malicious applications. + * Make sure to deny any unknown fragments here. + */ + protected boolean isValidFragment(String fragmentName) { + return PreferenceFragment.class.getName().equals(fragmentName) + || GeneralPreferenceFragment.class.getName().equals(fragmentName); + } + + /** + * This fragment shows general preferences only. It is used when the + * activity is showing a two-pane mainmenu UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class GeneralPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_general); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("pref_api_base")); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/activity/TorrentVideoPlayActivity.java b/app/src/main/java/net/schueller/peertube/activity/TorrentVideoPlayActivity.java new file mode 100644 index 0000000..3cb6451 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/TorrentVideoPlayActivity.java @@ -0,0 +1,217 @@ +package net.schueller.peertube.activity; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; + +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +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.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.BandwidthMeter; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.Util; + +import net.schueller.peertube.R; +import net.schueller.peertube.helper.APIUrlHelper; +import net.schueller.peertube.helper.MetaDataHelper; +import net.schueller.peertube.model.Video; + +import net.schueller.peertube.network.GetVideoDataService; +import net.schueller.peertube.network.RetrofitInstance; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class TorrentVideoPlayActivity extends AppCompatActivity { + + private static final String TAG = "TorrentVideoPlayActivity"; + + private ProgressBar progressBar; + private SimpleExoPlayer player; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_torrent_video_play); + + // get video ID + Intent intent = getIntent(); + String videoID = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); + Log.v(TAG, "click: " + videoID); + + progressBar = findViewById(R.id.progress); + progressBar.setMax(100); + + PlayerView videoView = findViewById(R.id.video_view); + + // 1. Create a default TrackSelector + BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + TrackSelection.Factory videoTrackSelectionFactory = + new AdaptiveTrackSelection.Factory(bandwidthMeter); + TrackSelector trackSelector = + new DefaultTrackSelector(videoTrackSelectionFactory); + + // 2. Create the player + player = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), trackSelector); + videoView.setPlayer(player); + + + TorrentOptions torrentOptions = new TorrentOptions.Builder() + .saveLocation(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)) + .removeFilesAfterStop(true) + .build(); + + TorrentStream torrentStream = TorrentStream.init(torrentOptions); + + torrentStream.addListener(new TorrentListener() { + @Override + public void onStreamReady(Torrent torrent) { + Log.d(TAG, "Ready"); + + setupVideoView(torrent); + + } + + @Override + public void onStreamProgress(Torrent torrent, StreamStatus streamStatus) { + if(streamStatus.bufferProgress <= 100 && progressBar.getProgress() < 100 && progressBar.getProgress() != streamStatus.bufferProgress) { + //Log.d(TAG, "Progress: " + streamStatus.bufferProgress); + progressBar.setProgress(streamStatus.bufferProgress); + } + } + + @Override + public void onStreamStopped() { + Log.d(TAG, "Stopped"); + } + + @Override + public void onStreamPrepared(Torrent torrent) { + Log.d(TAG, "Prepared"); + } + + @Override + public void onStreamStarted(Torrent torrent) { + Log.d(TAG, "Started"); + } + + @Override + public void onStreamError(Torrent torrent, Exception e) { + Log.d(TAG, "Error: " + e.getMessage()); + } + + }); + + // get video details from api + String apiBaseURL = APIUrlHelper.getUrl(this); + GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); + + Call