diff --git a/README.md b/README.md index 101c194..dd4fc90 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@

- screenshot - screenshot + screenshot + screenshot + screenshot

## Download @@ -23,16 +24,19 @@ Beta Test on Google Play: https://play.google.com/store/apps/details?id=net.schu - [X] Change Server - [X] Search - [X] App Icon and assets +- [X] Themes / Dark mode +- [X] Background playback +- [X] NSFW Filter option + +## TODO -# TODO - [ ] Video Playback via WebRTC -- [ ] Login +- [ ] Authentication / Login - [ ] Like/dislike video - [ ] Comment video - [ ] Unit Tests - - [ ] Lots more missing at this point... diff --git a/Screenshot_1545425431.png b/Screenshot_1545425431.png new file mode 100644 index 0000000..2d7c0f9 Binary files /dev/null and b/Screenshot_1545425431.png differ diff --git a/Screenshot_1545425504.png b/Screenshot_1545425504.png new file mode 100644 index 0000000..586bebc Binary files /dev/null and b/Screenshot_1545425504.png differ diff --git a/Screenshot_1545425516.png b/Screenshot_1545425516.png new file mode 100644 index 0000000..a1ae020 Binary files /dev/null and b/Screenshot_1545425516.png differ diff --git a/app/build.gradle b/app/build.gradle index ac5af8a..4ecb7fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,26 +6,24 @@ android { applicationId "net.schueller.peertube" minSdkVersion 21 targetSdkVersion 28 - versionCode 1010 - versionName "1.0.10" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + versionCode 1015 + versionName "1.0.15" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Layouts and design - implementation 'com.android.support.constraint:constraint-layout:1.1.3' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:cardview-v7:28.0.0' - implementation 'com.android.support:recyclerview-v7:28.0.0' - implementation 'com.android.support:design:28.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'com.google.android.material:material:1.0.0' implementation 'de.hdodenhof:circleimageview:2.2.0' - implementation 'com.android.support:support-v13:28.0.0' // font awesome - implementation 'com.blackboardtheory:android-iconify-fontawesome:3.0.1-SNAPSHOT' - - // BottomNavigationViewEx -> https://github.com/ittianyu/BottomNavigationViewEx - implementation 'com.github.ittianyu:BottomNavigationViewEx:2.0.2' + implementation "com.mikepenz:iconics-core:3.1.0" + implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar' // http client / REST implementation 'com.squareup.okhttp3:okhttp:3.10.0' @@ -49,12 +47,12 @@ android { implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.2' implementation 'com.google.android.exoplayer:exoplayer-hls:2.9.2' implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.9.2' -// implementation 'com.devbrackets.android:exomedia:4.1.0' + implementation 'com.google.android.exoplayer:extension-mediasession:2.9.2' // testing testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } } buildTypes { diff --git a/app/src/androidTest/java/net/schueller/peertube/ExampleInstrumentedTest.java b/app/src/androidTest/java/net/schueller/peertube/ExampleInstrumentedTest.java index 1079623..0c0fe04 100644 --- a/app/src/androidTest/java/net/schueller/peertube/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/net/schueller/peertube/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ package net.schueller.peertube; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d56e56e..5aa5606 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,33 +19,41 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> - - - + android:resource="@xml/searchable"> + + android:launchMode="singleTop" /> + + + + - - \ 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 index 8675d18..f4ef791 100644 --- a/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java @@ -3,11 +3,10 @@ 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 androidx.annotation.LayoutRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; + import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/net/schueller/peertube/activity/LoginActivity.java b/app/src/main/java/net/schueller/peertube/activity/LoginActivity.java index 40d44df..bbc5e96 100644 --- a/app/src/main/java/net/schueller/peertube/activity/LoginActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/LoginActivity.java @@ -21,6 +21,9 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; + public class LoginActivity extends AppCompatActivity { private String TAG = "LoginActivity"; @@ -32,6 +35,15 @@ public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Set theme + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); + setContentView(R.layout.activity_login); // bind button click diff --git a/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java b/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java index 9c06c8d..5f15750 100644 --- a/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java @@ -1,7 +1,7 @@ package net.schueller.peertube.activity; -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import net.schueller.peertube.R; diff --git a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java index f4070fd..0cb0211 100644 --- a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java @@ -3,11 +3,12 @@ package net.schueller.peertube.activity; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.preference.Preference; -import android.support.v7.app.ActionBar; +import androidx.appcompat.app.ActionBar; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; import android.util.Patterns; @@ -17,9 +18,13 @@ import android.widget.Toast; import net.schueller.peertube.R; import java.util.List; +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; public class SettingsActivity extends AppCompatPreferenceActivity { + private static String previousThemeColorValue = ""; + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -30,6 +35,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return super.onOptionsItemSelected(item); } + private static String getSelectedColor(Context context, String colorId){ + + String res = "Color not found"; + String [ ] themeArray = context.getResources().getStringArray(R.array.themeValues); + String [ ] colorArray = context.getResources().getStringArray(R.array.themeArray); + + for (int i = 0 ; i < themeArray.length ; i++){ + if (themeArray[i].equals(colorId)){ + res = colorArray[i]; + break; + } + } + return res; + } + /** * A preference value change listener that updates the preference's summary * to reflect its new value. @@ -42,6 +62,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity { Toast.makeText(preference.getContext(), R.string.invalid_url, Toast.LENGTH_LONG).show(); return false; } + // Check if Theme color has change & Provide selected color + else if (preference.getKey().equals("pref_theme")) { + + stringValue = getSelectedColor(preference.getContext(), stringValue); + + if (!previousThemeColorValue.equals("") && !previousThemeColorValue.equals(stringValue)) { + Toast.makeText(preference.getContext(), R.string.pref_description_app_theme, Toast.LENGTH_LONG).show(); + } + + previousThemeColorValue = stringValue; + preference.setSummary(stringValue); + return true; + } preference.setSummary(stringValue); @@ -80,10 +113,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { + + // Set theme + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); + super.onCreate(savedInstanceState); + setupActionBar(); getFragmentManager().beginTransaction().replace(android.R.id.content, new GeneralPreferenceFragment()).commit(); - } /** @@ -140,6 +182,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // updated to reflect the new value, per the Android Design // guidelines. bindPreferenceSummaryToValue(findPreference("pref_api_base")); + bindPreferenceSummaryToValue(findPreference("pref_theme")); } @Override diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java index 697a4b2..67514c3 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java @@ -8,17 +8,20 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.preference.PreferenceManager; import android.provider.SearchRecentSuggestions; -import android.support.annotation.NonNull; -import android.support.design.bottomnavigation.LabelVisibilityMode; -import android.support.v4.app.ActivityCompat; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; + +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.bottomnavigation.LabelVisibilityMode; +import androidx.core.app.ActivityCompat; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -26,17 +29,8 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; - -//import com.google.android.gms.common.GooglePlayServicesNotAvailableException; -//import com.google.android.gms.common.GooglePlayServicesRepairableException; -//import com.google.android.gms.common.GooglePlayServicesUtil; -//import com.google.android.gms.security.ProviderInstaller; -import com.ittianyu.bottomnavigationviewex.BottomNavigationViewEx; -import com.joanzapata.iconify.IconDrawable; -import com.joanzapata.iconify.Iconify; -import com.joanzapata.iconify.fonts.FontAwesomeIcons; -import com.joanzapata.iconify.fonts.FontAwesomeModule; - +import com.mikepenz.fontawesome_typeface_library.FontAwesome; +import com.mikepenz.iconics.IconicsDrawable; import net.schueller.peertube.R; import net.schueller.peertube.adapter.VideoAdapter; import net.schueller.peertube.helper.APIUrlHelper; @@ -44,6 +38,7 @@ import net.schueller.peertube.model.VideoList; import net.schueller.peertube.network.GetVideoDataService; import net.schueller.peertube.network.RetrofitInstance; import net.schueller.peertube.provider.SearchSuggestionsProvider; +import net.schueller.peertube.service.VideoPlayerService; import java.util.ArrayList; @@ -52,6 +47,9 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; + public class VideoListActivity extends AppCompatActivity { private String TAG = "VideoListActivity"; @@ -72,85 +70,35 @@ public class VideoListActivity extends AppCompatActivity { private boolean isLoading = false; - private BottomNavigationViewEx.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener - = item -> { - switch (item.getItemId()) { - case R.id.navigation_home: - //Log.v(TAG, "navigation_home"); - - if (!isLoading) { - sort = "-createdAt"; - currentStart = 0; - loadVideos(currentStart, count, sort, filter); - } - - return true; - case R.id.navigation_trending: - //Log.v(TAG, "navigation_trending"); - - if (!isLoading) { - sort = "-trending"; - currentStart = 0; - loadVideos(currentStart, count, sort, filter); - } - - return true; - case R.id.navigation_subscriptions: - //Log.v(TAG, "navigation_subscriptions"); - Toast.makeText(VideoListActivity.this, "Subscriptions Not Implemented", Toast.LENGTH_SHORT).show(); - - return false; - - case R.id.navigation_account: - //Log.v(TAG, "navigation_account"); -// Toast.makeText(VideoListActivity.this, "Account Not Implemented", Toast.LENGTH_SHORT).show(); - - Intent intent = new Intent(this, UserActivity.class); - this.startActivity(intent); - - return false; - } - return false; - }; @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Set Night Mode + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ? + AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + + // Set theme + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); + setContentView(R.layout.activity_video_list); filter = ""; - // Init icons - Iconify.with(new FontAwesomeModule()); + createBottomBarNavigation(); // Attaching the layout to the toolbar object Toolbar toolbar = findViewById(R.id.tool_bar); // Setting toolbar as the ActionBar with setSupportActionBar() call setSupportActionBar(toolbar); - // fix android trying to use SSLv3 for handshake -// updateAndroidSecurityProvider(this); - - // Bottom Navigation - BottomNavigationViewEx navigation = findViewById(R.id.navigation); - - navigation.enableAnimation(false); - navigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED); // enableShiftingMode - navigation.setItemHorizontalTranslationEnabled(false); // enableItemShiftingMode - - Menu navMenu = navigation.getMenu(); - navMenu.findItem(R.id.navigation_home).setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_home)); - navMenu.findItem(R.id.navigation_trending).setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_fire)); - navMenu.findItem(R.id.navigation_subscriptions).setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_folder)); - navMenu.findItem(R.id.navigation_account).setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_user_circle)); - - navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); - - // load Video List createList(); @@ -163,16 +111,12 @@ public class VideoListActivity extends AppCompatActivity { // Set an icon in the ActionBar menu.findItem(R.id.action_settings).setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_cog) - .colorRes(R.color.cardview_light_background) - .actionBarSize()); - + new IconicsDrawable(this, FontAwesome.Icon.faw_cog).actionBar()); MenuItem searchMenuItem = menu.findItem(R.id.action_search); + searchMenuItem.setIcon( - new IconDrawable(this, FontAwesomeIcons.fa_search) - .colorRes(R.color.cardview_light_background) - .actionBarSize()); + new IconicsDrawable(this, FontAwesome.Icon.faw_search).actionBar()); // Get the SearchView and set the searchable configuration SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); @@ -208,6 +152,11 @@ public class VideoListActivity extends AppCompatActivity { return true; } + @Override + protected void onDestroy() { + super.onDestroy(); + stopService(new Intent(this, VideoPlayerService.class)); + } @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -338,22 +287,7 @@ public class VideoListActivity extends AppCompatActivity { }); } - /** - * Force android to not use SSLv3 - *

- * // * @param callingActivity Activity - */ -// private void updateAndroidSecurityProvider(Activity callingActivity) { -// try { -// ProviderInstaller.installIfNeeded(this); -// } catch (GooglePlayServicesRepairableException e) { -// // Thrown when Google Play Services is not installed, up-to-date, or enabled -// // Show dialog to allow users to install, update, or otherwise enable Google Play services. -// GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0); -// } catch (GooglePlayServicesNotAvailableException e) { -// Log.e("SecurityException", "Google Play Services not available."); -// } -// } + @Override protected void onResume() { super.onResume(); @@ -397,4 +331,66 @@ public class VideoListActivity extends AppCompatActivity { } + private void createBottomBarNavigation() { + + // Get Bottom Navigation + BottomNavigationView navigation = findViewById(R.id.navigation); + + // Always show text label + navigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED); + + // Add Icon font + Menu navMenu = navigation.getMenu(); + navMenu.findItem(R.id.navigation_home).setIcon( + new IconicsDrawable(this, FontAwesome.Icon.faw_home)); + navMenu.findItem(R.id.navigation_trending).setIcon( + new IconicsDrawable(this, FontAwesome.Icon.faw_fire)); + navMenu.findItem(R.id.navigation_subscriptions).setIcon( + new IconicsDrawable(this, FontAwesome.Icon.faw_folder)); + navMenu.findItem(R.id.navigation_account).setIcon( + new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle)); + + // Click Listener + navigation.setOnNavigationItemSelectedListener(menuItem -> { + switch (menuItem.getItemId()) { + case R.id.navigation_home: + //Log.v(TAG, "navigation_home"); + + if (!isLoading) { + sort = "-createdAt"; + currentStart = 0; + loadVideos(currentStart, count, sort, filter); + } + + return true; + case R.id.navigation_trending: + //Log.v(TAG, "navigation_trending"); + + if (!isLoading) { + sort = "-trending"; + currentStart = 0; + loadVideos(currentStart, count, sort, filter); + } + + return true; + case R.id.navigation_subscriptions: + //Log.v(TAG, "navigation_subscriptions"); + Toast.makeText(VideoListActivity.this, "Subscriptions Not Implemented", Toast.LENGTH_SHORT).show(); + + return false; + + case R.id.navigation_account: + //Log.v(TAG, "navigation_account"); + Toast.makeText(VideoListActivity.this, "Account Not Implemented", Toast.LENGTH_SHORT).show(); + +// Intent intent = new Intent(this, LoginActivity.class); +// this.startActivity(intent); + + return false; + } + return false; + }); + + } + } diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java index b1cef54..fa781a2 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java @@ -12,14 +12,15 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.PopupMenu; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.PopupMenu; import android.util.Log; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; @@ -39,6 +40,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.squareup.picasso.Picasso; import net.schueller.peertube.R; +import net.schueller.peertube.fragment.VideoOptionsFragment; import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.MetaDataHelper; import net.schueller.peertube.intents.Intents; @@ -51,6 +53,9 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; + public class VideoPlayActivity extends AppCompatActivity implements VideoRendererEventListener { private static final String TAG = "VideoPlayActivity"; @@ -89,6 +94,15 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Set theme + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); + setContentView(R.layout.activity_video_play); progressBar = findViewById(R.id.progress); @@ -229,6 +243,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere TextView videoMeta = findViewById(R.id.videoMeta); ImageView avatarView = findViewById(R.id.avatar); ImageButton moreButton = findViewById(R.id.moreButton); + ImageButton videoOptions = findViewById(R.id.exo_more); + Video video = response.body(); @@ -275,8 +291,18 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere popup.show(); }); + // video player options + videoOptions.setOnClickListener(v -> { + + VideoOptionsFragment videoOptionsFragment = + VideoOptionsFragment.newInstance(mService); + videoOptionsFragment.show(getSupportFragmentManager(), + "video_options_fragment"); + }); + mService.setCurrentStreamUrl(video.getFiles().get(0).getFileUrl()); + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); if (sharedPref.getBoolean("pref_torrent_player", false)) { diff --git a/app/src/main/java/net/schueller/peertube/adapter/VideoAdapter.java b/app/src/main/java/net/schueller/peertube/adapter/VideoAdapter.java index d1b7796..b898133 100644 --- a/app/src/main/java/net/schueller/peertube/adapter/VideoAdapter.java +++ b/app/src/main/java/net/schueller/peertube/adapter/VideoAdapter.java @@ -2,9 +2,9 @@ package net.schueller.peertube.adapter; import android.content.Context; import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.v7.widget.PopupMenu; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.PopupMenu; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoOptionsFragment.java b/app/src/main/java/net/schueller/peertube/fragment/VideoOptionsFragment.java new file mode 100644 index 0000000..1ed17e0 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoOptionsFragment.java @@ -0,0 +1,78 @@ +package net.schueller.peertube.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.mikepenz.iconics.Iconics; +import net.schueller.peertube.R; +import net.schueller.peertube.service.VideoPlayerService; + +import androidx.annotation.Nullable; + +public class VideoOptionsFragment extends BottomSheetDialogFragment { + + private static VideoPlayerService videoPlayerService; + + private TextView speed05Icon; + private TextView speed10Icon; + private TextView speed15Icon; + private TextView speed20Icon; + + public static VideoOptionsFragment newInstance(VideoPlayerService mService) { + videoPlayerService = mService; + return new VideoOptionsFragment(); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.bottom_sheet_video_options_fragment, container, + false); + + // Icons + speed05Icon = view.findViewById(R.id.video_speed05_icon); + speed10Icon = view.findViewById(R.id.video_speed10_icon); + speed15Icon = view.findViewById(R.id.video_speed15_icon); + speed20Icon = view.findViewById(R.id.video_speed20_icon); + + // Buttons + TextView speed05 = view.findViewById(R.id.video_speed05); + TextView speed10 = view.findViewById(R.id.video_speed10); + TextView speed15 = view.findViewById(R.id.video_speed15); + TextView speed20 = view.findViewById(R.id.video_speed20); + + // Default + setVideoSpeed(1.0f, speed10Icon); + + // Attach the listener + speed05.setOnClickListener(v -> setVideoSpeed(0.5f, speed05Icon)); + speed10.setOnClickListener(v -> setVideoSpeed(1.0f, speed10Icon)); + speed15.setOnClickListener(v -> setVideoSpeed(1.5f, speed15Icon)); + speed20.setOnClickListener(v -> setVideoSpeed(2.0f, speed20Icon)); + + return view; + + } + + + private void setVideoSpeed(Float speed, TextView icon) { + + speed05Icon.setText(""); + speed10Icon.setText(""); + speed15Icon.setText(""); + speed20Icon.setText(""); + + videoPlayerService.setPlayBackSpeed(speed); + + icon.setText(R.string.video_speed_active_icon); + new Iconics.IconicsBuilder().ctx(getContext()).on(icon).build(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/helper/Constants.java b/app/src/main/java/net/schueller/peertube/helper/Constants.java new file mode 100644 index 0000000..02d03bf --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/helper/Constants.java @@ -0,0 +1,6 @@ +package net.schueller.peertube.helper; + +public class Constants { + public static final String THEME_PREF_KEY = "pref_theme"; + public static final String DEFAULT_THEME = "AppTheme.ORANGE"; +} diff --git a/app/src/main/java/net/schueller/peertube/model/Video.java b/app/src/main/java/net/schueller/peertube/model/Video.java index a640c73..377f645 100644 --- a/app/src/main/java/net/schueller/peertube/model/Video.java +++ b/app/src/main/java/net/schueller/peertube/model/Video.java @@ -1,8 +1,21 @@ package net.schueller.peertube.model; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.media.MediaDescriptionCompat; + +import com.squareup.picasso.Picasso; + +import net.schueller.peertube.R; +import net.schueller.peertube.helper.APIUrlHelper; + import java.util.ArrayList; import java.util.Date; +import androidx.annotation.DrawableRes; + public class Video { private Integer id; @@ -247,4 +260,31 @@ public class Video { public void setFiles(ArrayList files) { this.files = files; } + + + + public static MediaDescriptionCompat getMediaDescription(Context context, Video video) { + +// String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); + +// Bundle extras = new Bundle(); +// Bitmap bitmap = getBitmap(context, Uri.parse(apiBaseURL + video.thumbnailPath)); +// extras.putParcelable(MediaDescriptionCompat.DESCRIPTION_KEY_MEDIA_URI, bitmap); + + return new MediaDescriptionCompat.Builder() + .setMediaId(video.getUuid()) +// .setIconBitmap(bitmap) +// .setExtras(extras) + .setTitle(video.getName()) + .setDescription(video.getDescription()) + .build(); + } + +// TODO: add support for the thumbnail +// public static Bitmap getBitmap(Context context, Uri fullThumbnailUrl) { +// +// return Picasso.with(context).load(fullThumbnailUrl) +// .placeholder(R.drawable.ic_peertube) +// .error(R.drawable.ic_peertube).get(); +// } } diff --git a/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java b/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java index 2f42dd1..8eb4f14 100644 --- a/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java +++ b/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java @@ -11,13 +11,22 @@ import android.graphics.Bitmap; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; + +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.ui.PlayerNotificationManager; @@ -36,6 +45,9 @@ import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID; public class VideoPlayerService extends Service { + private static final String TAG = "VideoPlayerService"; + private static final String MEDIA_SESSION_TAG = "peertube_player"; + private final IBinder mBinder = new LocalBinder(); private static final String PLAYBACK_CHANNEL_ID = "playback_channel"; @@ -63,12 +75,12 @@ public class VideoPlayerService extends Service { public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { if (playbackState == ACTION_PAUSE) { // this means that pause is available, hence the audio is playing - Log.v("VideoPlayerService", "ACTION_PLAY: " + playbackState); + Log.v(TAG, "ACTION_PLAY: " + playbackState); registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter); } if (playbackState == ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped - Log.v("VideoPlayerService", "ACTION_PAUSE: " + playbackState); + Log.v(TAG, "ACTION_PAUSE: " + playbackState); unregisterReceiver(myNoisyAudioStreamReceiver); } } @@ -88,7 +100,7 @@ public class VideoPlayerService extends Service { @Override public void onDestroy() { - Log.v("VideoPlayerService", "onDestroy..."); + Log.v(TAG, "onDestroy..."); playerNotificationManager.setPlayer(null); player.release(); @@ -105,7 +117,7 @@ public class VideoPlayerService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.v("VideoPlayerService", "onStartCommand..."); + Log.v(TAG, "onStartCommand..."); playVideo(); return START_STICKY; } @@ -113,21 +125,27 @@ public class VideoPlayerService extends Service { public void setCurrentVideo(Video video) { - Log.v("VideoPlayerService", "setCurrentVideo..."); + Log.v(TAG, "setCurrentVideo..."); currentVideo = video; } public void setCurrentStreamUrl(String streamUrl) { - Log.v("VideoPlayerService", "setCurrentStreamUrl..."); + Log.v(TAG, "setCurrentStreamUrl..."); currentStreamUrl = streamUrl; } - public void playVideo() - { + //Playback speed control + public void setPlayBackSpeed(float speed) { + + Log.v(TAG, "setPlayBackSpeed..."); + player.setPlaybackParameters(new PlaybackParameters(speed)); + } + + public void playVideo() { Context context = this; - Log.v("VideoPlayerService", "playVideo..."); + Log.v(TAG, "playVideo..."); // Produces DataSource instances through which media data is loaded. DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(), @@ -143,6 +161,9 @@ public class VideoPlayerService extends Service { // Auto play player.setPlayWhenReady(true); + //reset playback speed + this.setPlayBackSpeed(1.0f); + playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel( context, PLAYBACK_CHANNEL_ID, R.string.playback_channel_name, PLAYBACK_NOTIFICATION_ID, @@ -193,7 +214,7 @@ public class VideoPlayerService extends Service { @Override public void onNotificationCancelled(int notificationId) { - Log.v("VideoPlayerService", "onNotificationCancelled..."); + Log.v(TAG, "onNotificationCancelled..."); // TODO: only kill the notification if we no longer have a bound activity stopForeground(true); @@ -203,6 +224,26 @@ public class VideoPlayerService extends Service { playerNotificationManager.setPlayer(player); + // external Media control, Android Wear / Google Home etc. + MediaSessionCompat mediaSession = new MediaSessionCompat(context, MEDIA_SESSION_TAG); + mediaSession.setActive(true); + playerNotificationManager.setMediaSessionToken(mediaSession.getSessionToken()); + MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mediaSession); + mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) { + @Override + public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) { + return Video.getMediaDescription(context, currentVideo); + } + }); + mediaSessionConnector.setPlayer(player, null); + + // Audio Focus + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.CONTENT_TYPE_MOVIE) + .build(); + player.setAudioAttributes(audioAttributes,true); + } // pause playback on audio output change @@ -215,4 +256,5 @@ public class VideoPlayerService extends Service { } } + } diff --git a/app/src/main/res/color/bottom_bar.xml b/app/src/main/res/color/bottom_bar.xml deleted file mode 100644 index e0f548d..0000000 --- a/app/src/main/res/color/bottom_bar.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1c90889..82d760d 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -30,7 +30,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - @@ -43,9 +43,9 @@ android:maxLines="1" android:singleLine="true" /> - + - @@ -61,7 +61,7 @@ android:maxLines="1" android:singleLine="true" /> - +