SponsorBlock: Merge branch 'dev' into sponsorblock

This commit is contained in:
polymorphicshade 2020-08-01 14:30:05 -06:00
commit 639c238d3f
130 changed files with 6718 additions and 5670 deletions

View file

@ -89,6 +89,7 @@ ext {
androidxRoomVersion = '2.2.5' androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.0' groupieVersion = '2.8.0'
markwonVersion = '4.3.1' markwonVersion = '4.3.1'
googleAutoServiceVersion = '1.0-rc7'
} }
configurations { configurations {
@ -175,6 +176,9 @@ dependencies {
implementation "com.google.android.material:material:1.1.0" implementation "com.google.android.material:material:1.1.0"
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.preference:preference:1.1.1" implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"

View file

@ -2,9 +2,9 @@ package org.schabi.newpipe.report;
import android.os.Parcel; import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest; import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;

View file

@ -1,6 +1,5 @@
package org.schabi.newpipe package org.schabi.newpipe
import android.content.Context
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
@ -11,11 +10,6 @@ import okhttp3.OkHttpClient
import org.schabi.newpipe.extractor.downloader.Downloader import org.schabi.newpipe.extractor.downloader.Downloader
class DebugApp : App() { class DebugApp : App() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initStetho() initStetho()
@ -34,6 +28,12 @@ class DebugApp : App() {
return downloader return downloader
} }
override fun initACRA() {
// install MultiDex before initializing ACRA
MultiDex.install(this)
super.initACRA()
}
private fun initStetho() { private fun initStetho() {
// Create an InitializerBuilder // Create an InitializerBuilder
val initializerBuilder = Stetho.newInitializerBuilder(this) val initializerBuilder = Stetho.newInitializerBuilder(this)

View file

@ -43,8 +43,8 @@
</receiver> </receiver>
<service <service
android:name=".player.BackgroundPlayer" android:name=".player.MainPlayer"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
@ -52,25 +52,9 @@
<activity <activity
android:name=".player.BackgroundPlayerActivity" android:name=".player.BackgroundPlayerActivity"
android:label="@string/title_activity_background_player" android:label="@string/title_activity_play_queue"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<activity
android:name=".player.PopupVideoPlayerActivity"
android:label="@string/title_activity_popup_player"
android:launchMode="singleTask" />
<service
android:name=".player.PopupVideoPlayer"
android:exported="false" />
<activity
android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/VideoPlayerTheme" />
<activity <activity
android:name=".settings.SettingsActivity" android:name=".settings.SettingsActivity"
android:label="@string/settings" /> android:label="@string/settings" />

View file

@ -4,11 +4,14 @@ import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.OverScroller; import android.widget.OverScroller;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import org.schabi.newpipe.R;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -20,6 +23,9 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
super(context, attrs); super(context, attrs);
} }
private boolean allowScroll = true;
private final Rect globalRect = new Rect();
@Override @Override
public boolean onRequestChildRectangleOnScreen( public boolean onRequestChildRectangleOnScreen(
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child, @NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
@ -55,6 +61,15 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child, public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
final MotionEvent ev) { final MotionEvent ev) {
final ViewGroup playQueue = child.findViewById(R.id.playQueuePanel);
if (playQueue != null) {
final boolean visible = playQueue.getGlobalVisibleRect(globalRect);
if (visible && globalRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
allowScroll = false;
return false;
}
}
allowScroll = true;
switch (ev.getActionMasked()) { switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// remove reference to old nested scrolling child // remove reference to old nested scrolling child
@ -68,6 +83,26 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return super.onInterceptTouchEvent(parent, child, ev); return super.onInterceptTouchEvent(parent, child, ev);
} }
@Override
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout parent,
@NonNull final AppBarLayout child,
@NonNull final View directTargetChild,
final View target,
final int nestedScrollAxes,
final int type) {
return allowScroll && super.onStartNestedScroll(
parent, child, directTargetChild, target, nestedScrollAxes, type);
}
@Override
public boolean onNestedFling(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final AppBarLayout child,
@NonNull final View target, final float velocityX,
final float velocityY, final boolean consumed) {
return allowScroll && super.onNestedFling(
coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Nullable @Nullable
private OverScroller getScrollerField() { private OverScroller getScrollerField() {
try { try {

View file

@ -20,10 +20,8 @@ import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException; import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration; import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder; import org.acra.config.CoreConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
@ -65,9 +63,6 @@ import io.reactivex.plugins.RxJavaPlugins;
public class App extends Application { public class App extends Application {
protected static final String TAG = App.class.toString(); protected static final String TAG = App.class.toString();
@SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[]
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
private static App app; private static App app;
public static App getApp() { public static App getApp() {
@ -77,7 +72,6 @@ public class App extends Application {
@Override @Override
protected void attachBaseContext(final Context base) { protected void attachBaseContext(final Context base) {
super.attachBaseContext(base); super.attachBaseContext(base);
initACRA(); initACRA();
} }
@ -200,10 +194,17 @@ public class App extends Application {
.build(); .build();
} }
private void initACRA() { /**
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
*/
protected void initACRA() {
if (ACRA.isACRASenderServiceProcess()) {
return;
}
try { try {
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this) final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class) .setBuildConfigClass(BuildConfig.class)
.build(); .build();
ACRA.init(this, acraConfig); ACRA.init(this, acraConfig);

View file

@ -29,6 +29,8 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -37,10 +39,10 @@ import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
@ -51,6 +53,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
@ -61,14 +64,18 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PeertubeHelper; import org.schabi.newpipe.util.PeertubeHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.TLSSocketFactoryCompat; import org.schabi.newpipe.util.TLSSocketFactoryCompat;
@ -137,7 +144,7 @@ public class MainActivity extends AppCompatActivity {
ErrorActivity.reportUiError(this, e); ErrorActivity.reportUiError(this, e);
} }
if (AndroidTvUtils.isTv(this)) { if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this); FocusOverlayView.setupFocusObserver(this);
} }
} }
@ -518,13 +525,27 @@ public class MainActivity extends AppCompatActivity {
handleIntent(intent); handleIntent(intent);
} }
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof OnKeyDownListener
&& !bottomSheetHiddenOrCollapsed()) {
// Provide keyDown event to fragment which then sends this event
// to the main player service
return ((OnKeyDownListener) fragment).onKeyDown(keyCode)
|| super.onKeyDown(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onBackPressed() called"); Log.d(TAG, "onBackPressed() called");
} }
if (AndroidTvUtils.isTv(this)) { if (DeviceUtils.isTv(this)) {
View drawerPanel = findViewById(R.id.navigation); View drawerPanel = findViewById(R.id.navigation);
if (drawer.isDrawerOpen(drawerPanel)) { if (drawer.isDrawerOpen(drawerPanel)) {
drawer.closeDrawers(); drawer.closeDrawers();
@ -532,11 +553,32 @@ public class MainActivity extends AppCompatActivity {
} }
} }
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); // In case bottomSheet is not visible on the screen or collapsed we can assume that the user
// If current fragment implements BackPressable (i.e. can/wanna handle back press) // interacts with a fragment inside fragment_holder so all back presses should be
// delegate the back press to it // handled by it
if (fragment instanceof BackPressable) { if (bottomSheetHiddenOrCollapsed()) {
if (((BackPressable) fragment).onBackPressed()) { final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
return;
}
}
} else {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragmentPlayer instanceof BackPressable) {
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
final FrameLayout bottomSheetLayout =
findViewById(R.id.fragment_player_holder);
BottomSheetBehavior.from(bottomSheetLayout)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return; return;
} }
} }
@ -563,7 +605,7 @@ public class MainActivity extends AppCompatActivity {
break; break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE: case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
Fragment fragment = getSupportFragmentManager() Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder); .findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof VideoDetailFragment) { if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog(); ((VideoDetailFragment) fragment).openDownloadDialog();
} }
@ -615,10 +657,6 @@ public class MainActivity extends AppCompatActivity {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (!(fragment instanceof VideoDetailFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
}
if (!(fragment instanceof SearchFragment)) { if (!(fragment instanceof SearchFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container) findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
.setVisibility(View.GONE); .setVisibility(View.GONE);
@ -660,6 +698,13 @@ public class MainActivity extends AppCompatActivity {
} }
StateSaver.clearStateFiles(); StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) { if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
// When user watch a video inside popup and then tries to open the video in main player
// while the app is closed he will see a blank fragment on place of kiosk.
// Let's open it first
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
NavigationHelper.openMainFragment(getSupportFragmentManager());
}
handleIntent(getIntent()); handleIntent(getIntent());
} else { } else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager()); NavigationHelper.gotoMainFragment(getSupportFragmentManager());
@ -708,8 +753,14 @@ public class MainActivity extends AppCompatActivity {
case STREAM: case STREAM:
boolean autoPlay = intent boolean autoPlay = intent
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false); .getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
final String intentCacheKey = intent
.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY);
final PlayQueue playQueue = intentCacheKey != null
? SerializedCache.getInstance()
.take(intentCacheKey, PlayQueue.class)
: null;
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
serviceId, url, title, autoPlay); serviceId, url, title, autoPlay, playQueue);
break; break;
case CHANNEL: case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(), NavigationHelper.openChannelFragment(getSupportFragmentManager(),
@ -742,4 +793,17 @@ public class MainActivity extends AppCompatActivity {
ErrorActivity.reportUiError(this, e); ErrorActivity.reportUiError(this, e);
} }
} }
/*
* Utils
* */
private boolean bottomSheetHiddenOrCollapsed() {
final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder);
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
BottomSheetBehavior.from(bottomSheetLayout);
final int sheetState = bottomSheetBehavior.getState();
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
}
} }

View file

@ -44,7 +44,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
@ -347,7 +347,7 @@ public class RouterActivity extends AppCompatActivity {
alertDialog.show(); alertDialog.show();
if (AndroidTvUtils.isTv(this)) { if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(alertDialog); FocusOverlayView.setupFocusObserver(alertDialog);
} }
} }
@ -701,7 +701,7 @@ public class RouterActivity extends AppCompatActivity {
playQueue = new SinglePlayQueue((StreamInfo) info); playQueue = new SinglePlayQueue((StreamInfo) info);
if (playerChoice.equals(videoPlayerKey)) { if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true); openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) { } else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) { } else if (playerChoice.equals(popupPlayerKey)) {
@ -716,7 +716,7 @@ public class RouterActivity extends AppCompatActivity {
: new PlaylistPlayQueue((PlaylistInfo) info); : new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) { if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true); openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) { } else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true); NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) { } else if (playerChoice.equals(popupPlayerKey)) {
@ -726,6 +726,11 @@ public class RouterActivity extends AppCompatActivity {
}; };
} }
private void openMainPlayer(final PlayQueue playQueue, final Choice choice) {
NavigationHelper.playOnMainPlayer(this, playQueue, choice.linkType,
choice.url, "", true, true);
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();

View file

@ -13,7 +13,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView; import org.schabi.newpipe.views.FocusOverlayView;
@ -57,7 +57,7 @@ public class DownloadActivity extends AppCompatActivity {
} }
}); });
if (AndroidTvUtils.isTv(this)) { if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this); FocusOverlayView.setupFocusObserver(this);
} }
} }

View file

@ -1,16 +1,29 @@
package org.schabi.newpipe.fragments.detail; package org.schabi.newpipe.fragments.detail;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import java.io.Serializable; import java.io.Serializable;
class StackItem implements Serializable { class StackItem implements Serializable {
private final int serviceId; private final int serviceId;
private final String url; private String url;
private String title; private String title;
private PlayQueue playQueue;
StackItem(final int serviceId, final String url, final String title) { StackItem(final int serviceId, final String url,
final String title, final PlayQueue playQueue) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.url = url; this.url = url;
this.title = title; this.title = title;
this.playQueue = playQueue;
}
public void setUrl(final String url) {
this.url = url;
}
public void setPlayQueue(final PlayQueue queue) {
this.playQueue = queue;
} }
public int getServiceId() { public int getServiceId() {
@ -29,6 +42,10 @@ class StackItem implements Serializable {
return url; return url;
} }
public PlayQueue getPlayQueue() {
return playQueue;
}
@Override @Override
public String toString() { public String toString() {
return getServiceId() + ":" + getUrl() + " > " + getTitle(); return getServiceId() + ":" + getUrl() + " > " + getTitle();

View file

@ -519,7 +519,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
monitorSubscription(result); monitorSubscription(result);
headerPlayAllButton.setOnClickListener(view -> NavigationHelper headerPlayAllButton.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue(), false)); .playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> NavigationHelper headerPopupButton.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false)); .playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> NavigationHelper headerBackgroundButton.setOnClickListener(view -> NavigationHelper

View file

@ -318,7 +318,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.subscribe(getPlaylistBookmarkSubscriber()); .subscribe(getPlaylistBookmarkSubscriber());
headerPlayAllButton.setOnClickListener(view -> headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> headerBackgroundButton.setOnClickListener(view ->

View file

@ -48,7 +48,7 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -482,16 +482,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) { if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
searchToolbarContainer.setTranslationX(100); searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0f); searchToolbarContainer.setAlpha(0.0f);
searchToolbarContainer.setVisibility(View.VISIBLE); searchToolbarContainer.setVisibility(View.VISIBLE);
searchToolbarContainer.animate() searchToolbarContainer.animate()
.translationX(0) .translationX(0)
.alpha(1f) .alpha(1.0f)
.setDuration(200) .setDuration(200)
.setInterpolator(new DecelerateInterpolator()).start(); .setInterpolator(new DecelerateInterpolator()).start();
} else { } else {
searchToolbarContainer.setTranslationX(0); searchToolbarContainer.setTranslationX(0);
searchToolbarContainer.setAlpha(1f); searchToolbarContainer.setAlpha(1.0f);
searchToolbarContainer.setVisibility(View.VISIBLE); searchToolbarContainer.setVisibility(View.VISIBLE);
} }
} }
@ -525,7 +525,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel(); showSuggestionsPanel();
} }
if (AndroidTvUtils.isTv(getContext())) { if (DeviceUtils.isTv(getContext())) {
showKeyboardSearch(); showKeyboardSearch();
} }
}); });

View file

@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
@ -126,7 +126,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemView.setOnLongClickListener(view -> { itemView.setOnLongClickListener(view -> {
if (AndroidTvUtils.isTv(itemBuilder.getContext())) { if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item); openCommentAuthor(item);
} else { } else {
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText); ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);

View file

@ -321,7 +321,7 @@ public class StatisticsPlaylistFragment
} }
headerPlayAllButton.setOnClickListener(view -> headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> headerBackgroundButton.setOnClickListener(view ->

View file

@ -492,7 +492,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
setVideoCount(itemListAdapter.getItemsList().size()); setVideoCount(itemListAdapter.getItemsList().size());
headerPlayAllButton.setOnClickListener(view -> headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> headerBackgroundButton.setOnClickListener(view ->

View file

@ -40,7 +40,7 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.Dia
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.PickerIconItem import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.AndroidTvUtils import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
class FeedGroupDialog : DialogFragment(), BackPressable { class FeedGroupDialog : DialogFragment(), BackPressable {
@ -237,7 +237,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
toolbar_search_edit_text.setOnClickListener { toolbar_search_edit_text.setOnClickListener {
if (AndroidTvUtils.isTv(context)) { if (DeviceUtils.isTv(context)) {
showKeyboardSearch() showKeyboardSearch()
} }
} }

View file

@ -1,684 +0,0 @@
/*
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
* BackgroundPlayer.java is part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.player;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.MediaSource;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.BitmapUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
/**
* Service Background Player implementing {@link VideoPlayer}.
*
* @author mauriciocolli
*/
public final class BackgroundPlayer extends Service {
public static final String ACTION_CLOSE
= "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE
= "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
public static final String ACTION_REPEAT
= "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String ACTION_PLAY_NEXT
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
public static final String ACTION_PLAY_PREVIOUS
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
public static final String ACTION_FAST_REWIND
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
private static final String TAG = "BackgroundPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int NOTIFICATION_ID = 123789;
private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
private BasePlayerImpl basePlayerImpl;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
private SharedPreferences sharedPreferences;
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private PlayerEventListener activityListener;
private IBinder mBinder;
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;
private boolean shouldUpdateOnProgress;
private int timesNotificationUpdated;
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
if (DEBUG) {
Log.d(TAG, "onCreate() called");
}
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
mBinder = new PlayerServiceBinder(basePlayerImpl);
shouldUpdateOnProgress = true;
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], "
+ "flags = [" + flags + "], startId = [" + startId + "]");
}
basePlayerImpl.handleIntent(intent);
if (basePlayerImpl.mediaSessionManager != null) {
basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
onClose();
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
}
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private void onClose() {
if (DEBUG) {
Log.d(TAG, "onClose() called");
}
if (basePlayerImpl != null) {
basePlayerImpl.savePlaybackState();
basePlayerImpl.stopActivityBinding();
basePlayerImpl.destroy();
}
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}
mBinder = null;
basePlayerImpl = null;
stopForeground(true);
stopSelf();
}
private void onScreenOnOff(final boolean on) {
if (DEBUG) {
Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
}
shouldUpdateOnProgress = on;
basePlayerImpl.triggerProgressUpdate();
if (on) {
basePlayerImpl.startProgressLoop();
} else {
basePlayerImpl.stopProgressLoop();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private void resetNotification() {
notBuilder = createNotification();
timesNotificationUpdated = 0;
}
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_background_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_background_notification_expanded);
setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);
NotificationCompat.Builder builder = new NotificationCompat
.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLockScreenThumbnail(builder);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX);
}
return builder;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
if (isLockScreenThumbnailEnabled) {
basePlayerImpl.mediaSessionManager.setLockScreenArt(
builder,
getCenteredThumbnailBitmap()
);
} else {
basePlayerImpl.mediaSessionManager.clearLockScreenArt(builder);
}
}
@Nullable
private Bitmap getCenteredThumbnailBitmap() {
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
return BitmapUtils.centerCrop(basePlayerImpl.getThumbnail(), screenWidth, screenHeight);
}
private void setupNotification(final RemoteViews remoteViews) {
if (basePlayerImpl == null) {
return;
}
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts background player activity -- attempts to unlock lockscreen
final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this);
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
PendingIntent.FLAG_UPDATE_CURRENT));
if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_previous);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_next);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
} else {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_rewind);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_fastforward);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
}
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
}
/**
* Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/
private synchronized void updateNotification(final int drawableId) {
// if (DEBUG) {
// Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
// }
if (notBuilder == null) {
return;
}
if (drawableId != -1) {
if (notRemoteView != null) {
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
}
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
timesNotificationUpdated++;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_repeat_all);
break;
}
}
//////////////////////////////////////////////////////////////////////////
protected class BasePlayerImpl extends BasePlayer {
@NonNull
private final AudioPlaybackResolver resolver;
private int cachedDuration;
private String cachedDurationString;
BasePlayerImpl(final Context context) {
super(context);
this.resolver = new AudioPlaybackResolver(context, dataSource);
}
@Override
public void initPlayer(final boolean playOnReady) {
super.initPlayer(playOnReady);
}
@Override
public void handleIntent(final Intent intent) {
super.handleIntent(intent);
resetNotification();
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
startForeground(NOTIFICATION_ID, notBuilder.build());
}
/*//////////////////////////////////////////////////////////////////////////
// Thumbnail Loading
//////////////////////////////////////////////////////////////////////////*/
private void updateNotificationThumbnail() {
if (basePlayerImpl == null) {
return;
}
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover,
basePlayerImpl.getThumbnail());
}
}
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
@Override
public void onLoadingFailed(final String imageUri, final View view,
final FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
}
/*//////////////////////////////////////////////////////////////////////////
// States Implementation
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onPrepared(final boolean playWhenReady) {
super.onPrepared(playWhenReady);
}
@Override
public void onShuffleClicked() {
super.onShuffleClicked();
updatePlayback();
}
@Override
public void onMuteUnmuteButtonClicked() {
super.onMuteUnmuteButtonClicked();
updatePlayback();
}
@Override
public void onUpdateProgress(final int currentProgress, final int duration,
final int bufferPercent) {
updateProgress(currentProgress, duration, bufferPercent);
if (!shouldUpdateOnProgress) {
return;
}
if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
resetNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) {
updateNotificationThumbnail();
}
}
if (bigNotRemoteView != null) {
if (cachedDuration != duration) {
cachedDuration = duration;
cachedDurationString = getTimeString(duration);
}
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime,
getTimeString(currentProgress) + " / " + cachedDurationString);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
currentProgress, false);
}
updateNotification(-1);
}
@Override
public void onPlayPrevious() {
super.onPlayPrevious();
triggerProgressUpdate();
}
@Override
public void onPlayNext() {
super.onPlayNext();
triggerProgressUpdate();
}
@Override
public void destroy() {
super.destroy();
if (notRemoteView != null) {
notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
super.onPlaybackParametersChanged(playbackParameters);
updatePlayback();
}
@Override
public void onLoadingChanged(final boolean isLoading) {
// Disable default behavior
}
@Override
public void onRepeatModeChanged(final int i) {
resetNotification();
updateNotification(-1);
updatePlayback();
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
resetNotification();
updateNotificationThumbnail();
updateNotification(-1);
updateMetadata();
}
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
return resolver.resolve(info);
}
@Override
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
onClose();
}
/*//////////////////////////////////////////////////////////////////////////
// Activity Event Listener
//////////////////////////////////////////////////////////////////////////*/
/*package-private*/ void setActivityListener(final PlayerEventListener listener) {
activityListener = listener;
updateMetadata();
updatePlayback();
triggerProgressUpdate();
}
/*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
if (activityListener == listener) {
activityListener = null;
}
}
private void updateMetadata() {
if (activityListener != null && getCurrentMetadata() != null) {
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
}
}
private void updatePlayback() {
if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
playQueue.isShuffled(), getPlaybackParameters());
}
}
private void updateProgress(final int currentProgress, final int duration,
final int bufferPercent) {
if (activityListener != null) {
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
}
private void stopActivityBinding() {
if (activityListener != null) {
activityListener.onServiceStopped();
activityListener = null;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
super.setupBroadcastReceiver(intentFltr);
intentFltr.addAction(ACTION_CLOSE);
intentFltr.addAction(ACTION_PLAY_PAUSE);
intentFltr.addAction(ACTION_REPEAT);
intentFltr.addAction(ACTION_PLAY_PREVIOUS);
intentFltr.addAction(ACTION_PLAY_NEXT);
intentFltr.addAction(ACTION_FAST_REWIND);
intentFltr.addAction(ACTION_FAST_FORWARD);
intentFltr.addAction(Intent.ACTION_SCREEN_ON);
intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
intentFltr.addAction(Intent.ACTION_HEADSET_PLUG);
}
@Override
public void onBroadcastReceived(final Intent intent) {
super.onBroadcastReceived(intent);
if (intent == null || intent.getAction() == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
}
switch (intent.getAction()) {
case ACTION_CLOSE:
onClose();
break;
case ACTION_PLAY_PAUSE:
onPlayPause();
break;
case ACTION_REPEAT:
onRepeatClicked();
break;
case ACTION_PLAY_NEXT:
onPlayNext();
break;
case ACTION_PLAY_PREVIOUS:
onPlayPrevious();
break;
case ACTION_FAST_FORWARD:
onFastForward();
break;
case ACTION_FAST_REWIND:
onFastRewind();
break;
case Intent.ACTION_SCREEN_ON:
onScreenOnOff(true);
break;
case Intent.ACTION_SCREEN_OFF:
onScreenOnOff(false);
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// States
//////////////////////////////////////////////////////////////////////////*/
@Override
public void changeState(final int state) {
super.changeState(state);
updatePlayback();
}
@Override
public void onPlaying() {
super.onPlaying();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.exo_controls_pause);
}
@Override
public void onPaused() {
super.onPaused();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.exo_controls_play);
}
@Override
public void onCompleted() {
super.onCompleted();
resetNotification();
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
updateNotificationThumbnail();
updateNotification(R.drawable.ic_replay_white_24dp);
}
}
}

View file

@ -1,13 +1,13 @@
package org.schabi.newpipe.player; package org.schabi.newpipe.player;
import android.content.Intent; import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
public final class BackgroundPlayerActivity extends ServicePlayerActivity { public final class BackgroundPlayerActivity extends ServicePlayerActivity {
private static final String TAG = "BackgroundPlayerActivity"; private static final String TAG = "BackgroundPlayerActivity";
@ -19,25 +19,25 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@Override @Override
public String getSupportActionTitle() { public String getSupportActionTitle() {
return getResources().getString(R.string.title_activity_background_player); return getResources().getString(R.string.title_activity_play_queue);
} }
@Override @Override
public Intent getBindIntent() { public Intent getBindIntent() {
return new Intent(this, BackgroundPlayer.class); return new Intent(this, MainPlayer.class);
} }
@Override @Override
public void startPlayerListener() { public void startPlayerListener() {
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) { if (player instanceof VideoPlayerImpl) {
((BackgroundPlayer.BasePlayerImpl) player).setActivityListener(this); ((VideoPlayerImpl) player).setActivityListener(this);
} }
} }
@Override @Override
public void stopPlayerListener() { public void stopPlayerListener() {
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) { if (player instanceof VideoPlayerImpl) {
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this); ((VideoPlayerImpl) player).removeActivityListener(this);
} }
} }
@ -56,18 +56,30 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
} }
this.player.setRecovery(); this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); NavigationHelper.playOnPopupPlayer(
getApplicationContext().startService( getApplicationContext(), player.playQueue, this.player.isPlaying());
getSwitchIntent(PopupVideoPlayer.class)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
return true; return true;
} }
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(
getApplicationContext(), player.playQueue, this.player.isPlaying());
return true;
}
return false; return false;
} }
@Override @Override
public Intent getPlayerShutdownIntent() { public void setupMenu(final Menu menu) {
return new Intent(ACTION_CLOSE); if (player == null) {
return;
}
menu.findItem(R.id.action_switch_popup)
.setVisible(!((VideoPlayerImpl) player).popupPlayerSelected());
menu.findItem(R.id.action_switch_background)
.setVisible(!((VideoPlayerImpl) player).audioPlayerSelected());
} }
} }

View file

@ -54,6 +54,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import io.reactivex.android.schedulers.AndroidSchedulers;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.DownloaderImpl;
@ -136,13 +137,15 @@ public abstract class BasePlayer implements
@NonNull @NonNull
public static final String SELECT_ON_APPEND = "select_on_append"; public static final String SELECT_ON_APPEND = "select_on_append";
@NonNull @NonNull
public static final String PLAYER_TYPE = "player_type";
@NonNull
public static final String IS_MUTED = "is_muted"; public static final String IS_MUTED = "is_muted";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Playback // Playback
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
protected PlayQueue playQueue; protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter; protected PlayQueueAdapter playQueueAdapter;
@ -167,6 +170,10 @@ public abstract class BasePlayer implements
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
public static final int PLAYER_TYPE_VIDEO = 0;
public static final int PLAYER_TYPE_AUDIO = 1;
public static final int PLAYER_TYPE_POPUP = 2;
protected SimpleExoPlayer simpleExoPlayer; protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor; protected AudioReactor audioReactor;
protected MediaSessionManager mediaSessionManager; protected MediaSessionManager mediaSessionManager;
@ -233,7 +240,7 @@ public abstract class BasePlayer implements
public void setup() { public void setup() {
if (simpleExoPlayer == null) { if (simpleExoPlayer == null) {
initPlayer(/*playOnInit=*/true); initPlayer(true);
} }
initListeners(); initListeners();
} }
@ -260,7 +267,8 @@ public abstract class BasePlayer implements
registerBroadcastReceiver(); registerBroadcastReceiver();
} }
public void initListeners() { } public void initListeners() {
}
public void handleIntent(final Intent intent) { public void handleIntent(final Intent intent) {
if (DEBUG) { if (DEBUG) {
@ -298,34 +306,72 @@ public abstract class BasePlayer implements
final float playbackPitch = savedParameters.pitch; final float playbackPitch = savedParameters.pitch;
final boolean playbackSkipSilence = savedParameters.skipSilence; final boolean playbackSkipSilence = savedParameters.skipSilence;
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
final boolean isMuted = intent final boolean isMuted = intent
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted()); .getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
/*
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
* 2. User changed a player from, for example. main to popup, or from audio to main, etc
* 3. User chose to resume a video based on a saved timestamp from history of played videos
* In those cases time will be saved because re-init of the play queue is a not an instant
* task and requires network calls
* */
// seek to timestamp if stream is already playing // seek to timestamp if stream is already playing
if (simpleExoPlayer != null if (simpleExoPlayer != null
&& queue.size() == 1 && queue.size() == 1
&& playQueue != null && playQueue != null
&& playQueue.size() == 1
&& playQueue.getItem() != null && playQueue.getItem() != null
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl()) && queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET && queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
) { // Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
simpleExoPlayer.retry();
}
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition()); simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
return; return;
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
} else if (samePlayQueue && !playQueue.isDisposed() && simpleExoPlayer != null) {
// Do not re-init the same PlayQueue. Save time
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
simpleExoPlayer.retry();
}
return;
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
&& isPlaybackResumeEnabled()
&& !samePlayQueue) {
final PlayQueueItem item = queue.getItem(); final PlayQueueItem item = queue.getItem();
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) { if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
stateLoader = recordManager.loadStreamState(item) stateLoader = recordManager.loadStreamState(item)
.observeOn(mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed, // Do not place initPlayback() in doFinally() because
playbackPitch, playbackSkipSilence, true, isMuted)) // it restarts playback after destroy()
//.doFinally()
.subscribe( .subscribe(
state -> queue state -> {
.setRecovery(queue.getIndex(), state.getProgressTime()), queue.setRecovery(queue.getIndex(), state.getProgressTime());
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
},
error -> { error -> {
if (DEBUG) { if (DEBUG) {
error.printStackTrace(); error.printStackTrace();
} }
// In case any error we can start playback without history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
},
() -> {
// Completed but not found in history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
} }
); );
databaseUpdateReactor.add(stateLoader); databaseUpdateReactor.add(stateLoader);
@ -333,8 +379,11 @@ public abstract class BasePlayer implements
} }
} }
// Good to go... // Good to go...
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, // In a case of equal PlayQueues we can re-init old one but only when it is disposed
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted); initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
playbackSpeed, playbackPitch, playbackSkipSilence,
!intent.getBooleanExtra(START_PAUSED, false),
isMuted);
} }
private PlaybackParameters retrievePlaybackParametersFromPreferences() { private PlaybackParameters retrievePlaybackParametersFromPreferences() {
@ -420,6 +469,7 @@ public abstract class BasePlayer implements
databaseUpdateReactor.clear(); databaseUpdateReactor.clear();
progressUpdateReactor.set(null); progressUpdateReactor.set(null);
ImageLoader.getInstance().stop();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -571,7 +621,8 @@ public abstract class BasePlayer implements
} }
} }
public void onPausedSeek() { } public void onPausedSeek() {
}
public void onCompleted() { public void onCompleted() {
if (DEBUG) { if (DEBUG) {
@ -1137,6 +1188,7 @@ public abstract class BasePlayer implements
} }
simpleExoPlayer.setPlayWhenReady(true); simpleExoPlayer.setPlayWhenReady(true);
savePlaybackState();
} }
public void onPause() { public void onPause() {
@ -1149,6 +1201,7 @@ public abstract class BasePlayer implements
audioReactor.abandonAudioFocus(); audioReactor.abandonAudioFocus();
simpleExoPlayer.setPlayWhenReady(false); simpleExoPlayer.setPlayWhenReady(false);
savePlaybackState();
} }
public void onPlayPause() { public void onPlayPause() {
@ -1481,6 +1534,10 @@ public abstract class BasePlayer implements
return simpleExoPlayer != null && simpleExoPlayer.isPlaying(); return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
} }
public boolean isLoading() {
return simpleExoPlayer != null && simpleExoPlayer.isLoading();
}
@Player.RepeatMode @Player.RepeatMode
public int getRepeatMode() { public int getRepeatMode() {
return simpleExoPlayer == null return simpleExoPlayer == null
@ -1521,8 +1578,9 @@ public abstract class BasePlayer implements
/** /**
* Sets the playback parameters of the player, and also saves them to shared preferences. * Sets the playback parameters of the player, and also saves them to shared preferences.
* Speed and pitch are rounded up to 2 decimal places before being used or saved. * Speed and pitch are rounded up to 2 decimal places before being used or saved.
* @param speed the playback speed, will be rounded to up to 2 decimal places *
* @param pitch the playback pitch, will be rounded to up to 2 decimal places * @param speed the playback speed, will be rounded to up to 2 decimal places
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
* @param skipSilence skip silence during playback * @param skipSilence skip silence during playback
*/ */
public void setPlaybackParameters(final float speed, final float pitch, public void setPlaybackParameters(final float speed, final float pitch,
@ -1538,11 +1596,11 @@ public abstract class BasePlayer implements
private void savePlaybackParametersToPreferences(final float speed, final float pitch, private void savePlaybackParametersToPreferences(final float speed, final float pitch,
final boolean skipSilence) { final boolean skipSilence) {
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
.edit() .edit()
.putFloat(context.getString(R.string.playback_speed_key), speed) .putFloat(context.getString(R.string.playback_speed_key), speed)
.putFloat(context.getString(R.string.playback_pitch_key), pitch) .putFloat(context.getString(R.string.playback_pitch_key), pitch)
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence) .putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
.apply(); .apply();
} }
public PlayQueue getPlayQueue() { public PlayQueue getPlayQueue() {

View file

@ -0,0 +1,483 @@
/*
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
* Part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.player;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.BitmapUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
/**
* One service for all players.
*
* @author mauriciocolli
*/
public final class MainPlayer extends Service {
private static final String TAG = "MainPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private VideoPlayerImpl playerImpl;
private WindowManager windowManager;
private SharedPreferences sharedPreferences;
private final IBinder mBinder = new MainPlayer.LocalBinder();
public enum PlayerType {
VIDEO,
AUDIO,
POPUP
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
static final int NOTIFICATION_ID = 123789;
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;
static final String ACTION_CLOSE =
"org.schabi.newpipe.player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE =
"org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
static final String ACTION_OPEN_CONTROLS =
"org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT =
"org.schabi.newpipe.player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT =
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
static final String ACTION_PLAY_PREVIOUS =
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
static final String ACTION_FAST_REWIND =
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
static final String ACTION_FAST_FORWARD =
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
private static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
if (DEBUG) {
Log.d(TAG, "onCreate() called");
}
assureCorrectAppLanguage(this);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
ThemeHelper.setTheme(this);
createView();
}
private void createView() {
final View layout = View.inflate(this, R.layout.player, null);
playerImpl = new VideoPlayerImpl(this);
playerImpl.setup(layout);
playerImpl.shouldUpdateOnProgress = true;
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent
+ "], flags = [" + flags + "], startId = [" + startId + "]");
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
&& playerImpl.playQueue == null) {
// Player is not working, no need to process media button's action
return START_NOT_STICKY;
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|| intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
showNotificationAndStartForeground();
}
playerImpl.handleIntent(intent);
if (playerImpl.mediaSessionManager != null) {
playerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
}
return START_NOT_STICKY;
}
public void stop(final boolean autoplayEnabled) {
if (DEBUG) {
Log.d(TAG, "stop() called");
}
if (playerImpl.getPlayer() != null) {
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
// Releases wifi & cpu, disables keepScreenOn, etc.
if (!autoplayEnabled) {
playerImpl.onPause();
}
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth
playerImpl.getPlayer().stop(false);
playerImpl.setRecovery();
// Android TV will handle back button in case controls will be visible
// (one more additional unneeded click while the player is hidden)
playerImpl.hideControls(0, 0);
// Notification shows information about old stream but if a user selects
// a stream from backStack it's not actual anymore
// So we should hide the notification at all.
// When autoplay enabled such notification flashing is annoying so skip this case
if (!autoplayEnabled) {
stopForeground(true);
}
}
}
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
onDestroy();
// Unload from memory completely
Runtime.getRuntime().halt(0);
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
onClose();
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
}
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private void onClose() {
if (DEBUG) {
Log.d(TAG, "onClose() called");
}
if (playerImpl != null) {
removeViewFromParent();
playerImpl.setRecovery();
playerImpl.savePlaybackState();
playerImpl.stopActivityBinding();
playerImpl.removePopupFromView();
playerImpl.destroy();
}
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}
stopForeground(true);
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
final DisplayMetrics metrics = (playerImpl != null
&& playerImpl.getParentActivity() != null)
? playerImpl.getParentActivity().getResources().getDisplayMetrics()
: getResources().getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
}
public View getView() {
if (playerImpl == null) {
return null;
}
return playerImpl.getRootView();
}
public void removeViewFromParent() {
if (getView().getParent() != null) {
if (playerImpl.getParentActivity() != null) {
// This means view was added to fragment
final ViewGroup parent = (ViewGroup) getView().getParent();
parent.removeView(getView());
} else {
// This means view was added by windowManager for popup player
windowManager.removeViewImmediate(getView());
}
}
}
private void showNotificationAndStartForeground() {
resetNotification();
if (getBigNotRemoteView() != null) {
getBigNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
if (getNotRemoteView() != null) {
getNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
startForeground(NOTIFICATION_ID, getNotBuilder().build());
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
void resetNotification() {
notBuilder = createNotification();
playerImpl.timesNotificationUpdated = 0;
}
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_notification_expanded);
setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);
final NotificationCompat.Builder builder = new NotificationCompat
.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLockScreenThumbnail(builder);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX);
}
return builder;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
final boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
if (isLockScreenThumbnailEnabled) {
playerImpl.mediaSessionManager.setLockScreenArt(
builder,
getCenteredThumbnailBitmap()
);
} else {
playerImpl.mediaSessionManager.clearLockScreenArt(builder);
}
}
@Nullable
private Bitmap getCenteredThumbnailBitmap() {
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
return BitmapUtils.centerCrop(playerImpl.getThumbnail(), screenWidth, screenHeight);
}
private void setupNotification(final RemoteViews remoteViews) {
// Don't show anything until player is playing
if (playerImpl == null) {
return;
}
remoteViews.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
remoteViews.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts VideoDetailFragment or opens BackgroundPlayerActivity.
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID,
getIntentForNotification(), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
if (playerImpl.playQueue != null && playerImpl.playQueue.size() > 1) {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_previous);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_next);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
} else {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_rewind);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_fastforward);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
}
setRepeatModeIcon(remoteViews, playerImpl.getRepeatMode());
}
/**
* Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/
synchronized void updateNotification(final int drawableId) {
/*if (DEBUG) {
Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
}*/
if (notBuilder == null) {
return;
}
if (drawableId != -1) {
if (notRemoteView != null) {
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
}
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
playerImpl.timesNotificationUpdated++;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
if (remoteViews == null) {
return;
}
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
break;
}
}
private Intent getIntentForNotification() {
final Intent intent;
if (playerImpl.audioPlayerSelected() || playerImpl.popupPlayerSelected()) {
// Means we play in popup or audio only. Let's show BackgroundPlayerActivity
intent = NavigationHelper.getBackgroundPlayerActivityIntent(getApplicationContext());
} else {
// We are playing in fragment. Don't open another activity just show fragment. That's it
intent = NavigationHelper.getPlayerIntent(this, MainActivity.class, null, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
return intent;
}
/*//////////////////////////////////////////////////////////////////////////
// Getters
//////////////////////////////////////////////////////////////////////////*/
NotificationCompat.Builder getNotBuilder() {
return notBuilder;
}
RemoteViews getBigNotRemoteView() {
return bigNotRemoteView;
}
RemoteViews getNotRemoteView() {
return notRemoteView;
}
public class LocalBinder extends Binder {
public MainPlayer getService() {
return MainPlayer.this;
}
public VideoPlayerImpl getPlayer() {
return MainPlayer.this.playerImpl;
}
}
}

View file

@ -1,66 +0,0 @@
package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import static org.schabi.newpipe.player.PopupVideoPlayer.ACTION_CLOSE;
public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
private static final String TAG = "PopupVideoPlayerActivity";
@Override
public String getTag() {
return TAG;
}
@Override
public String getSupportActionTitle() {
return getResources().getString(R.string.title_activity_popup_player);
}
@Override
public Intent getBindIntent() {
return new Intent(this, PopupVideoPlayer.class);
}
@Override
public void startPlayerListener() {
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
((PopupVideoPlayer.VideoPlayerImpl) player).setActivityListener(this);
}
}
@Override
public void stopPlayerListener() {
if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
((PopupVideoPlayer.VideoPlayerImpl) player).removeActivityListener(this);
}
}
@Override
public int getPlayerOptionMenuResource() {
return R.menu.menu_play_queue_popup;
}
@Override
public boolean onPlayerOptionSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startService(
getSwitchIntent(BackgroundPlayer.class)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
return true;
}
return false;
}
@Override
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
}
}

View file

@ -27,17 +27,21 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -110,7 +114,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public abstract boolean onPlayerOptionSelected(MenuItem item); public abstract boolean onPlayerOptionSelected(MenuItem item);
public abstract Intent getPlayerShutdownIntent(); public abstract void setupMenu(Menu m);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle // Activity Lifecycle
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -152,6 +156,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true; return true;
} }
// Allow to setup visibility of menuItems
@Override
public boolean onPrepareOptionsMenu(final Menu m) {
setupMenu(m);
return super.onPrepareOptionsMenu(m);
}
@Override @Override
public boolean onOptionsItemSelected(final MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@ -175,11 +186,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true; return true;
case R.id.action_switch_main: case R.id.action_switch_main:
this.player.setRecovery(); this.player.setRecovery();
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startActivity( getApplicationContext().startActivity(
getSwitchIntent(MainVideoPlayer.class) getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()) .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
);
return true; return true;
} }
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item); return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
@ -191,13 +200,22 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
unbind(); unbind();
} }
protected Intent getSwitchIntent(final Class clazz) { protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz, return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
this.player.getPlayQueue(), this.player.getRepeatMode(), this.player.getPlayQueue(), this.player.getRepeatMode(),
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(), this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted()) this.player.getPlaybackSkipSilence(),
null,
true,
!this.player.isPlaying(),
this.player.isMuted())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()); .putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM)
.putExtra(Constants.KEY_URL, this.player.getVideoUrl())
.putExtra(Constants.KEY_TITLE, this.player.getVideoTitle())
.putExtra(Constants.KEY_SERVICE_ID,
this.player.getCurrentMetadata().getMetadata().getServiceId())
.putExtra(VideoPlayer.PLAYER_TYPE, playerType);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -247,6 +265,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (service instanceof PlayerServiceBinder) { if (service instanceof PlayerServiceBinder) {
player = ((PlayerServiceBinder) service).getPlayerInstance(); player = ((PlayerServiceBinder) service).getPlayerInstance();
} else if (service instanceof MainPlayer.LocalBinder) {
player = ((MainPlayer.LocalBinder) service).getPlayer();
} }
if (player == null || player.getPlayQueue() == null if (player == null || player.getPlayQueue() == null
@ -500,7 +520,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return; return;
} }
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag()); player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
} }
@Override @Override
@ -571,6 +591,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Binding Service Listener // Binding Service Listener
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@Override
public void onQueueUpdate(final PlayQueue queue) {
}
@Override @Override
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled, public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
final PlaybackParameters parameters) { final PlaybackParameters parameters) {
@ -610,7 +634,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
} }
@Override @Override
public void onMetadataUpdate(final StreamInfo info) { public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (info != null) { if (info != null) {
metadataTitle.setText(info.getName()); metadataTitle.setText(info.getName());
metadataArtist.setText(info.getUploaderName()); metadataArtist.setText(info.getUploaderName());

View file

@ -34,16 +34,16 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
@ -69,6 +69,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView;
import org.schabi.newpipe.util.SponsorTimeInfo; import org.schabi.newpipe.util.SponsorTimeInfo;
import org.schabi.newpipe.util.TimeFrame; import org.schabi.newpipe.util.TimeFrame;
import org.schabi.newpipe.views.MarkableSeekBar; import org.schabi.newpipe.views.MarkableSeekBar;
@ -121,8 +122,7 @@ public abstract class VideoPlayer extends BasePlayer
private View rootView; private View rootView;
private AspectRatioFrameLayout aspectRatioFrameLayout; private ExpandableSurfaceView surfaceView;
private SurfaceView surfaceView;
private View surfaceForeground; private View surfaceForeground;
private View loadingPanel; private View loadingPanel;
@ -139,7 +139,7 @@ public abstract class VideoPlayer extends BasePlayer
private TextView playbackLiveSync; private TextView playbackLiveSync;
private TextView playbackSpeedTextView; private TextView playbackSpeedTextView;
private View topControlsRoot; private LinearLayout topControlsRoot;
private TextView qualityTextView; private TextView qualityTextView;
private SubtitleView subtitleView; private SubtitleView subtitleView;
@ -186,7 +186,6 @@ public abstract class VideoPlayer extends BasePlayer
public void initViews(final View view) { public void initViews(final View view) {
this.rootView = view; this.rootView = view;
this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
this.surfaceView = view.findViewById(R.id.surfaceView); this.surfaceView = view.findViewById(R.id.surfaceView);
this.surfaceForeground = view.findViewById(R.id.surfaceForeground); this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
this.loadingPanel = view.findViewById(R.id.loading_panel); this.loadingPanel = view.findViewById(R.id.loading_panel);
@ -211,12 +210,10 @@ public abstract class VideoPlayer extends BasePlayer
this.resizeView = view.findViewById(R.id.resizeTextView); this.resizeView = view.findViewById(R.id.resizeTextView);
resizeView.setText(PlayerHelper resizeView.setText(PlayerHelper
.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode())); .resizeTypeOf(context, getSurfaceView().getResizeMode()));
this.captionTextView = view.findViewById(R.id.captionTextView); this.captionTextView = view.findViewById(R.id.captionTextView);
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
} }
@ -524,7 +521,6 @@ public abstract class VideoPlayer extends BasePlayer
super.onCompleted(); super.onCompleted();
showControls(500); showControls(500);
animateView(endScreen, true, 800);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
loadingPanel.setVisibility(View.GONE); loadingPanel.setVisibility(View.GONE);
@ -559,7 +555,7 @@ public abstract class VideoPlayer extends BasePlayer
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], " + "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]"); + "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
} }
aspectRatioFrameLayout.setAspectRatio(((float) width) / height); getSurfaceView().setAspectRatio(((float) width) / height);
} }
@Override @Override
@ -626,12 +622,6 @@ public abstract class VideoPlayer extends BasePlayer
super.onPrepared(playWhenReady); super.onPrepared(playWhenReady);
markSponsorTimes(); markSponsorTimes();
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler
.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
}
} }
private void markSponsorTimes() { private void markSponsorTimes() {
@ -706,7 +696,7 @@ public abstract class VideoPlayer extends BasePlayer
} }
} }
protected void onFullScreenButtonClicked() { protected void toggleFullscreen() {
changeState(STATE_BLOCKED); changeState(STATE_BLOCKED);
} }
@ -830,16 +820,16 @@ public abstract class VideoPlayer extends BasePlayer
showControls(DEFAULT_CONTROLS_DURATION); showControls(DEFAULT_CONTROLS_DURATION);
} }
private void onResizeClicked() { void onResizeClicked() {
if (getAspectRatioFrameLayout() != null) { if (getSurfaceView() != null) {
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode(); final int currentResizeMode = getSurfaceView().getResizeMode();
final int newResizeMode = nextResizeMode(currentResizeMode); final int newResizeMode = nextResizeMode(currentResizeMode);
setResizeMode(newResizeMode); setResizeMode(newResizeMode);
} }
} }
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
getAspectRatioFrameLayout().setResizeMode(resizeMode); getSurfaceView().setResizeMode(resizeMode);
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode)); getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
} }
@ -947,9 +937,9 @@ public abstract class VideoPlayer extends BasePlayer
if (drawableId == -1) { if (drawableId == -1) {
if (controlAnimationView.getVisibility() == View.VISIBLE) { if (controlAnimationView.getVisibility() == View.VISIBLE) {
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView, controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f), PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f), PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f) PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
).setDuration(DEFAULT_CONTROLS_DURATION); ).setDuration(DEFAULT_CONTROLS_DURATION);
controlViewAnimator.addListener(new AnimatorListenerAdapter() { controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
@ -1051,6 +1041,9 @@ public abstract class VideoPlayer extends BasePlayer
animateView(controlsRoot, false, duration); animateView(controlsRoot, false, duration);
}; };
} }
public abstract void hideSystemUIIfNeeded();
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Getters and Setters // Getters and Setters
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -1064,11 +1057,7 @@ public abstract class VideoPlayer extends BasePlayer
this.resolver.setPlaybackQuality(quality); this.resolver.setPlaybackQuality(quality);
} }
public AspectRatioFrameLayout getAspectRatioFrameLayout() { public ExpandableSurfaceView getSurfaceView() {
return aspectRatioFrameLayout;
}
public SurfaceView getSurfaceView() {
return surfaceView; return surfaceView;
} }
@ -1127,7 +1116,7 @@ public abstract class VideoPlayer extends BasePlayer
return playbackEndTime; return playbackEndTime;
} }
public View getTopControlsRoot() { public LinearLayout getTopControlsRoot() {
return topControlsRoot; return topControlsRoot;
} }
@ -1139,6 +1128,10 @@ public abstract class VideoPlayer extends BasePlayer
return qualityPopupMenu; return qualityPopupMenu;
} }
public TextView getPlaybackSpeedTextView() {
return playbackSpeedTextView;
}
public PopupMenu getPlaybackSpeedPopupMenu() { public PopupMenu getPlaybackSpeedPopupMenu() {
return playbackSpeedPopupMenu; return playbackSpeedPopupMenu;
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
package org.schabi.newpipe.player.event;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.schabi.newpipe.R;
import java.util.Arrays;
import java.util.List;
public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout> {
public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
boolean visible;
Rect globalRect = new Rect();
private boolean skippingInterception = false;
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
R.id.playQueuePanel, R.id.viewpager);
@Override
public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
@NonNull final FrameLayout child,
final MotionEvent event) {
// Drop following when action ends
if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
skippingInterception = false;
}
// Found that user still swiping, continue following
if (skippingInterception) {
return false;
}
// Don't need to do anything if bottomSheet isn't expanded
if (getState() == BottomSheetBehavior.STATE_EXPANDED) {
// Without overriding scrolling will not work when user touches these elements
for (final Integer element : skipInterceptionOfElements) {
final ViewGroup viewGroup = child.findViewById(element);
if (viewGroup != null) {
visible = viewGroup.getGlobalVisibleRect(globalRect);
if (visible
&& globalRect.contains((int) event.getRawX(), (int) event.getRawY())) {
skippingInterception = true;
return false;
}
}
}
}
return super.onInterceptTouchEvent(parent, child, event);
}
}

View file

@ -0,0 +1,5 @@
package org.schabi.newpipe.player.event;
public interface OnKeyDownListener {
boolean onKeyDown(int keyCode);
}

View file

@ -4,14 +4,13 @@ package org.schabi.newpipe.player.event;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueue;
public interface PlayerEventListener { public interface PlayerEventListener {
void onQueueUpdate(PlayQueue queue);
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
PlaybackParameters parameters); PlaybackParameters parameters);
void onProgressUpdate(int currentProgress, int duration, int bufferPercent); void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
void onMetadataUpdate(StreamInfo info, PlayQueue queue);
void onMetadataUpdate(StreamInfo info);
void onServiceStopped(); void onServiceStopped();
} }

View file

@ -0,0 +1,622 @@
package org.schabi.newpipe.player.event;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManager;
import androidx.appcompat.content.res.AppCompatResources;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl;
import org.schabi.newpipe.player.helper.PlayerHelper;
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class PlayerGestureListener
extends GestureDetector.SimpleOnGestureListener
implements View.OnTouchListener {
private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG;
private final VideoPlayerImpl playerImpl;
private final MainPlayer service;
private int initialPopupX;
private int initialPopupY;
private boolean isMovingInMain;
private boolean isMovingInPopup;
private boolean isResizing;
private final int tossFlingVelocity;
private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled;
private final int maxVolume;
private static final int MOVEMENT_THRESHOLD = 40;
// [popup] initial coordinates and distance between fingers
private double initPointerDistance = -1;
private float initFirstPointerX = -1;
private float initFirstPointerY = -1;
private float initSecPointerX = -1;
private float initSecPointerY = -1;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
this.playerImpl = playerImpl;
this.service = service;
this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
}
/*//////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////*/
/*
* Main and popup players' gesture listeners is too different.
* So it will be better to have different implementations of them
* */
@Override
public boolean onDoubleTap(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = "
+ e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
}
if (playerImpl.popupPlayerSelected()) {
return onDoubleTapInPopup(e);
} else {
return onDoubleTapInMain(e);
}
}
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onSingleTapConfirmedInPopup(e);
} else {
return onSingleTapConfirmedInMain(e);
}
}
@Override
public boolean onDown(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDown() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onDownInPopup(e);
} else {
return true;
}
}
@Override
public void onLongPress(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
onLongPressInPopup(e);
}
}
@Override
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (playerImpl.popupPlayerSelected()) {
return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
} else {
return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
}
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
final float velocityX, final float velocityY) {
if (DEBUG) {
Log.d(TAG, "onFling() called with velocity: dX=["
+ velocityX + "], dY=[" + velocityY + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onFlingInPopup(e1, e2, velocityX, velocityY);
} else {
return true;
}
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
/*if (DEBUG && false) {
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
}*/
if (playerImpl.popupPlayerSelected()) {
return onTouchInPopup(v, event);
} else {
return onTouchInMain(v, event);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Main player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInMain(final MotionEvent e) {
if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
playerImpl.onFastForward();
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
playerImpl.onFastRewind();
} else {
playerImpl.getPlayPauseButton().performClick();
}
return true;
}
private boolean onSingleTapConfirmedInMain(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
return true;
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
} else {
playerImpl.showControlsThenHide();
}
}
return true;
}
private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) {
return false;
}
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service);
final boolean isTouchingNavigationBar = initialEvent.getY()
> playerImpl.getRootView().getHeight() - getNavigationBarHeight(service);
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false;
}
/*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " +
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
final boolean insideThreshold =
Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return false;
}
isMovingInMain = true;
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
boolean acceptVolumeArea = acceptAnyArea
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
if (isVolumeGestureEnabled && acceptVolumeArea) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
} else {
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return true;
}
final Window window = parent.getWindow();
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl.getBrightnessProgressBar()
.getProgress() / playerImpl.getMaxGestureLength();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}
return true;
}
private void onScrollEndInMain() {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
}
private boolean onTouchInMain(final View v, final MotionEvent event) {
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) {
isMovingInMain = false;
onScrollEndInMain();
}
// This hack allows to stop receiving touch events on appbar
// while touching video player's view
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
return true;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
return false;
default:
return true;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Popup player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInPopup(final MotionEvent e) {
if (playerImpl == null || !playerImpl.isPlaying()) {
return false;
}
playerImpl.hideControls(0, 0);
if (e.getX() > playerImpl.getPopupWidth() / 2) {
playerImpl.onFastForward();
} else {
playerImpl.onFastRewind();
}
return true;
}
private boolean onSingleTapConfirmedInPopup(final MotionEvent e) {
if (playerImpl == null || playerImpl.getPlayer() == null) {
return false;
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(100, 100);
} else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide();
}
return true;
}
private boolean onDownInPopup(final MotionEvent e) {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
initialPopupX = playerImpl.getPopupLayoutParams().x;
initialPopupY = playerImpl.getPopupLayoutParams().y;
playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
return super.onDown(e);
}
private void onLongPressInPopup(final MotionEvent e) {
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
}
private boolean onScrollInPopup(final MotionEvent initialEvent,
final MotionEvent movingEvent,
final float distanceX,
final float distanceY) {
if (isResizing || playerImpl == null) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
}
if (!isMovingInPopup) {
animateView(playerImpl.getCloseOverlayButton(), true, 200);
}
isMovingInPopup = true;
final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
float posX = (int) (initialPopupX + diffX);
final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
float posY = (int) (initialPopupY + diffY);
if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
} else if (posX < 0) {
posX = 0;
}
if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
} else if (posY < 0) {
posY = 0;
}
playerImpl.getPopupLayoutParams().x = (int) posX;
playerImpl.getPopupLayoutParams().y = (int) posY;
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
// if (DEBUG) {
// Log.d(TAG, "onScrollInPopup = "
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
// + initialEvent.getRawY() + "], "
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
// + initialEvent.getY() + "], "
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
// + movingEvent.getRawY() + "], "
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
// + "posX,Y = [" + posX + ", " + posY + "], "
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
// }
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
private void onScrollEndInPopup(final MotionEvent event) {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
}
private boolean onFlingInPopup(final MotionEvent e1,
final MotionEvent e2,
final float velocityX,
final float velocityY) {
if (playerImpl == null) {
return false;
}
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().x = (int) velocityX;
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().y = (int) velocityY;
}
playerImpl.checkPopupPositionBounds();
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
return false;
}
private boolean onTouchInPopup(final View v, final MotionEvent event) {
if (playerImpl == null) {
return false;
}
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
}
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
//record coordinates of fingers
initFirstPointerX = event.getX(0);
initFirstPointerY = event.getY(0);
initSecPointerX = event.getX(1);
initSecPointerY = event.getY(1);
//record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
initFirstPointerY - initSecPointerY);
isResizing = true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
return handleMultiDrag(event);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
if (isMovingInPopup) {
isMovingInPopup = false;
onScrollEndInPopup(event);
}
if (isResizing) {
isResizing = false;
initPointerDistance = -1;
initFirstPointerX = -1;
initFirstPointerY = -1;
initSecPointerX = -1;
initSecPointerY = -1;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize();
}
}
v.performClick();
return true;
}
private boolean handleMultiDrag(final MotionEvent event) {
if (initPointerDistance != -1 && event.getPointerCount() == 2) {
// get the movements of the fingers
double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
event.getY(0) - initFirstPointerY);
double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
event.getY(1) - initSecPointerY);
// minimum threshold beyond which pinch gesture will work
int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
final double currentPointerDistance =
Math.hypot(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
double popupWidth = playerImpl.getPopupWidth();
// change co-ordinates of popup so the center stays at the same position
double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
initPointerDistance = currentPointerDistance;
playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
playerImpl.checkPopupPositionBounds();
playerImpl.updateScreenSize();
playerImpl.updatePopupSize(
(int) Math.min(playerImpl.getScreenWidth(), newWidth),
-1);
return true;
}
}
return false;
}
/*
* Utils
* */
private int getNavigationBarHeight(final Context context) {
int resId = context.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
private int getStatusBarHeight(final Context context) {
int resId = context.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
}

View file

@ -0,0 +1,15 @@
package org.schabi.newpipe.player.event;
import com.google.android.exoplayer2.ExoPlaybackException;
public interface PlayerServiceEventListener extends PlayerEventListener {
void onFullscreenStateChanged(boolean fullscreen);
void onScreenRotationButtonClicked();
void onMoreOptionsLongClicked();
void onPlayerError(ExoPlaybackException error);
void hideSystemUiIfNeeded();
}

View file

@ -114,7 +114,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
private void onAudioFocusGain() { private void onAudioFocusGain() {
Log.d(TAG, "onAudioFocusGain() called"); Log.d(TAG, "onAudioFocusGain() called");
player.setVolume(DUCK_AUDIO_TO); player.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f); animateAudio(DUCK_AUDIO_TO, 1.0f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) { if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true); player.setPlayWhenReady(true);

View file

@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
public static PlaybackParameterDialog newInstance(final double playbackTempo, public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch, final double playbackPitch,
final boolean playbackSkipSilence) { final boolean playbackSkipSilence,
final Callback callback) {
PlaybackParameterDialog dialog = new PlaybackParameterDialog(); PlaybackParameterDialog dialog = new PlaybackParameterDialog();
dialog.callback = callback;
dialog.initialTempo = playbackTempo; dialog.initialTempo = playbackTempo;
dialog.initialPitch = playbackPitch; dialog.initialPitch = playbackPitch;
@ -111,9 +113,9 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override @Override
public void onAttach(final Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
if (context != null && context instanceof Callback) { if (context instanceof Callback) {
callback = (Callback) context; callback = (Callback) context;
} else { } else if (callback == null) {
dismiss(); dismiss();
} }
} }

View file

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
@ -45,6 +46,9 @@ import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MOD
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT; import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM; import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
@ -56,6 +60,15 @@ public final class PlayerHelper {
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
@Retention(SOURCE)
@IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
AUTOPLAY_TYPE_NEVER})
public @interface AutoplayType {
int AUTOPLAY_TYPE_ALWAYS = 0;
int AUTOPLAY_TYPE_WIFI = 1;
int AUTOPLAY_TYPE_NEVER = 2;
}
private PlayerHelper() { } private PlayerHelper() { }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -203,6 +216,11 @@ public final class PlayerHelper {
return isAutoQueueEnabled(context, false); return isAutoQueueEnabled(context, false);
} }
public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.clear_queue_confirmation_key), false);
}
@MinimizeMode @MinimizeMode
public static int getMinimizeOnExitAction(@NonNull final Context context) { public static int getMinimizeOnExitAction(@NonNull final Context context) {
final String defaultAction = context.getString(R.string.minimize_on_exit_none_key); final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
@ -219,6 +237,18 @@ public final class PlayerHelper {
} }
} }
@AutoplayType
public static int getAutoplayType(@NonNull final Context context) {
final String type = getAutoplayType(context, context.getString(R.string.autoplay_wifi_key));
if (type.equals(context.getString(R.string.autoplay_always_key))) {
return AUTOPLAY_TYPE_ALWAYS;
} else if (type.equals(context.getString(R.string.autoplay_never_key))) {
return AUTOPLAY_TYPE_NEVER;
} else {
return AUTOPLAY_TYPE_WIFI;
}
}
@NonNull @NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) { public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT; return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
@ -308,7 +338,7 @@ public final class PlayerHelper {
final CaptioningManager captioningManager final CaptioningManager captioningManager
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager == null || !captioningManager.isEnabled()) { if (captioningManager == null || !captioningManager.isEnabled()) {
return 1f; return 1.0f;
} }
return captioningManager.getFontScale(); return captioningManager.getFontScale();
@ -324,6 +354,13 @@ public final class PlayerHelper {
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis()); setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
} }
public static boolean globalScreenOrientationLocked(final Context context) {
// 1: Screen orientation changes using accelerometer
// 0: Screen orientation is locked
return android.provider.Settings.System.getInt(
context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Private helpers // Private helpers
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -396,6 +433,12 @@ public final class PlayerHelper {
.getString(context.getString(R.string.minimize_on_exit_key), key); .getString(context.getString(R.string.minimize_on_exit_key), key);
} }
private static String getAutoplayType(@NonNull final Context context,
final String key) {
return getPreferences(context).getString(context.getString(R.string.autoplay_key),
key);
}
private static SinglePlayQueue getAutoQueuedSinglePlayQueue( private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
final StreamInfoItem streamInfoItem) { final StreamInfoItem streamInfoItem) {
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem); SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);

View file

@ -51,16 +51,24 @@ public abstract class PlayQueue implements Serializable {
@NonNull @NonNull
private final AtomicInteger queueIndex; private final AtomicInteger queueIndex;
private final ArrayList<PlayQueueItem> history;
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast; private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
private transient Flowable<PlayQueueEvent> broadcastReceiver; private transient Flowable<PlayQueueEvent> broadcastReceiver;
private transient Subscription reportingReactor; private transient Subscription reportingReactor;
private transient boolean disposed;
PlayQueue(final int index, final List<PlayQueueItem> startWith) { PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = new ArrayList<>(); streams = new ArrayList<>();
streams.addAll(startWith); streams.addAll(startWith);
history = new ArrayList<>();
if (streams.size() > index) {
history.add(streams.get(index));
}
queueIndex = new AtomicInteger(index); queueIndex = new AtomicInteger(index);
disposed = false;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -99,6 +107,7 @@ public abstract class PlayQueue implements Serializable {
eventBroadcast = null; eventBroadcast = null;
broadcastReceiver = null; broadcastReceiver = null;
reportingReactor = null; reportingReactor = null;
disposed = true;
} }
/** /**
@ -149,6 +158,9 @@ public abstract class PlayQueue implements Serializable {
if (index >= streams.size()) { if (index >= streams.size()) {
newIndex = isComplete() ? index % streams.size() : streams.size() - 1; newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
} }
if (oldIndex != newIndex) {
history.add(streams.get(newIndex));
}
queueIndex.set(newIndex); queueIndex.set(newIndex);
broadcast(new SelectEvent(oldIndex, newIndex)); broadcast(new SelectEvent(oldIndex, newIndex));
@ -269,7 +281,7 @@ public abstract class PlayQueue implements Serializable {
* @param items {@link PlayQueueItem}s to append * @param items {@link PlayQueueItem}s to append
*/ */
public synchronized void append(@NonNull final List<PlayQueueItem> items) { public synchronized void append(@NonNull final List<PlayQueueItem> items) {
List<PlayQueueItem> itemList = new ArrayList<>(items); final List<PlayQueueItem> itemList = new ArrayList<>(items);
if (isShuffled()) { if (isShuffled()) {
backup.addAll(itemList); backup.addAll(itemList);
@ -314,6 +326,9 @@ public abstract class PlayQueue implements Serializable {
public synchronized void error() { public synchronized void error() {
final int oldIndex = getIndex(); final int oldIndex = getIndex();
queueIndex.incrementAndGet(); queueIndex.incrementAndGet();
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
broadcast(new ErrorEvent(oldIndex, getIndex())); broadcast(new ErrorEvent(oldIndex, getIndex()));
} }
@ -334,7 +349,11 @@ public abstract class PlayQueue implements Serializable {
if (backup != null) { if (backup != null) {
backup.remove(getItem(removeIndex)); backup.remove(getItem(removeIndex));
} }
streams.remove(removeIndex);
history.remove(streams.remove(removeIndex));
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
} }
/** /**
@ -367,7 +386,7 @@ public abstract class PlayQueue implements Serializable {
queueIndex.incrementAndGet(); queueIndex.incrementAndGet();
} }
PlayQueueItem playQueueItem = streams.remove(source); final PlayQueueItem playQueueItem = streams.remove(source);
playQueueItem.setAutoQueued(false); playQueueItem.setAutoQueued(false);
streams.add(target, playQueueItem); streams.add(target, playQueueItem);
broadcast(new MoveEvent(source, target)); broadcast(new MoveEvent(source, target));
@ -427,6 +446,9 @@ public abstract class PlayQueue implements Serializable {
streams.add(0, streams.remove(newIndex)); streams.add(0, streams.remove(newIndex));
} }
queueIndex.set(0); queueIndex.set(0);
if (streams.size() > 0) {
history.add(streams.get(0));
}
broadcast(new ReorderEvent(originIndex, queueIndex.get())); broadcast(new ReorderEvent(originIndex, queueIndex.get()));
} }
@ -458,10 +480,60 @@ public abstract class PlayQueue implements Serializable {
} else { } else {
queueIndex.set(0); queueIndex.set(0);
} }
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
broadcast(new ReorderEvent(originIndex, queueIndex.get())); broadcast(new ReorderEvent(originIndex, queueIndex.get()));
} }
/**
* Selects previous played item.
*
* This method removes currently playing item from history and
* starts playing the last item from history if it exists
*
* @return true if history is not empty and the item can be played
* */
public synchronized boolean previous() {
if (history.size() <= 1) {
return false;
}
history.remove(history.size() - 1);
final PlayQueueItem last = history.remove(history.size() - 1);
setIndex(indexOf(last));
return true;
}
/*
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
* we don't have to do anything with new queue.
* This method also gives a chance to track history of items in a queue in
* VideoDetailFragment without duplicating items from two identical queues
* */
@Override
public boolean equals(@Nullable final Object obj) {
if (!(obj instanceof PlayQueue)
|| getStreams().size() != ((PlayQueue) obj).getStreams().size()) {
return false;
}
final PlayQueue other = (PlayQueue) obj;
for (int i = 0; i < getStreams().size(); i++) {
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) {
return false;
}
}
return true;
}
public boolean isDisposed() {
return disposed;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Rx Broadcast // Rx Broadcast
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View file

@ -4,9 +4,12 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.auto.service.AutoService;
import org.acra.config.CoreConfiguration; import org.acra.config.CoreConfiguration;
import org.acra.sender.ReportSender; import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderFactory; import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.App;
/* /*
* Created by Christian Schabesberger on 13.09.16. * Created by Christian Schabesberger on 13.09.16.
@ -28,6 +31,10 @@ import org.acra.sender.ReportSenderFactory;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
/**
* Used by ACRA in {@link App}.initAcra() as the factory for report senders.
*/
@AutoService(ReportSenderFactory.class)
public class AcraReportSenderFactory implements ReportSenderFactory { public class AcraReportSenderFactory implements ReportSenderFactory {
@NonNull @NonNull
public ReportSender create(@NonNull final Context context, public ReportSender create(@NonNull final Context context,

View file

@ -13,7 +13,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView; import org.schabi.newpipe.views.FocusOverlayView;
@ -62,7 +62,7 @@ public class SettingsActivity extends AppCompatActivity
.commit(); .commit();
} }
if (AndroidTvUtils.isTv(this)) { if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this); FocusOverlayView.setupFocusObserver(this);
} }
} }

View file

@ -1,416 +1,416 @@
package org.schabi.newpipe.streams; package org.schabi.newpipe.streams;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.schabi.newpipe.streams.WebMReader.Cluster; import org.schabi.newpipe.streams.WebMReader.Cluster;
import org.schabi.newpipe.streams.WebMReader.Segment; import org.schabi.newpipe.streams.WebMReader.Segment;
import org.schabi.newpipe.streams.WebMReader.SimpleBlock; import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
import org.schabi.newpipe.streams.WebMReader.WebMTrack; import org.schabi.newpipe.streams.WebMReader.WebMTrack;
import org.schabi.newpipe.streams.io.SharpStream; import org.schabi.newpipe.streams.io.SharpStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
/** /**
* @author kapodamy * @author kapodamy
*/ */
public class OggFromWebMWriter implements Closeable { public class OggFromWebMWriter implements Closeable {
private static final byte FLAG_UNSET = 0x00; private static final byte FLAG_UNSET = 0x00;
//private static final byte FLAG_CONTINUED = 0x01; //private static final byte FLAG_CONTINUED = 0x01;
private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_FIRST = 0x02;
private static final byte FLAG_LAST = 0x04; private static final byte FLAG_LAST = 0x04;
private static final byte HEADER_CHECKSUM_OFFSET = 22; private static final byte HEADER_CHECKSUM_OFFSET = 22;
private static final byte HEADER_SIZE = 27; private static final byte HEADER_SIZE = 27;
private static final int TIME_SCALE_NS = 1000000000; private static final int TIME_SCALE_NS = 1000000000;
private boolean done = false; private boolean done = false;
private boolean parsed = false; private boolean parsed = false;
private SharpStream source; private SharpStream source;
private SharpStream output; private SharpStream output;
private int sequenceCount = 0; private int sequenceCount = 0;
private final int streamId; private final int streamId;
private byte packetFlag = FLAG_FIRST; private byte packetFlag = FLAG_FIRST;
private WebMReader webm = null; private WebMReader webm = null;
private WebMTrack webmTrack = null; private WebMTrack webmTrack = null;
private Segment webmSegment = null; private Segment webmSegment = null;
private Cluster webmCluster = null; private Cluster webmCluster = null;
private SimpleBlock webmBlock = null; private SimpleBlock webmBlock = null;
private long webmBlockLastTimecode = 0; private long webmBlockLastTimecode = 0;
private long webmBlockNearDuration = 0; private long webmBlockNearDuration = 0;
private short segmentTableSize = 0; private short segmentTableSize = 0;
private final byte[] segmentTable = new byte[255]; private final byte[] segmentTable = new byte[255];
private long segmentTableNextTimestamp = TIME_SCALE_NS; private long segmentTableNextTimestamp = TIME_SCALE_NS;
private final int[] crc32Table = new int[256]; private final int[] crc32Table = new int[256];
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) { public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
if (!source.canRead() || !source.canRewind()) { if (!source.canRead() || !source.canRewind()) {
throw new IllegalArgumentException("source stream must be readable and allows seeking"); throw new IllegalArgumentException("source stream must be readable and allows seeking");
} }
if (!target.canWrite() || !target.canRewind()) { if (!target.canWrite() || !target.canRewind()) {
throw new IllegalArgumentException("output stream must be writable and allows seeking"); throw new IllegalArgumentException("output stream must be writable and allows seeking");
} }
this.source = source; this.source = source;
this.output = target; this.output = target;
this.streamId = (int) System.currentTimeMillis(); this.streamId = (int) System.currentTimeMillis();
populateCrc32Table(); populateCrc32Table();
} }
public boolean isDone() { public boolean isDone() {
return done; return done;
} }
public boolean isParsed() { public boolean isParsed() {
return parsed; return parsed;
} }
public WebMTrack[] getTracksFromSource() throws IllegalStateException { public WebMTrack[] getTracksFromSource() throws IllegalStateException {
if (!parsed) { if (!parsed) {
throw new IllegalStateException("source must be parsed first"); throw new IllegalStateException("source must be parsed first");
} }
return webm.getAvailableTracks(); return webm.getAvailableTracks();
} }
public void parseSource() throws IOException, IllegalStateException { public void parseSource() throws IOException, IllegalStateException {
if (done) { if (done) {
throw new IllegalStateException("already done"); throw new IllegalStateException("already done");
} }
if (parsed) { if (parsed) {
throw new IllegalStateException("already parsed"); throw new IllegalStateException("already parsed");
} }
try { try {
webm = new WebMReader(source); webm = new WebMReader(source);
webm.parse(); webm.parse();
webmSegment = webm.getNextSegment(); webmSegment = webm.getNextSegment();
} finally { } finally {
parsed = true; parsed = true;
} }
} }
public void selectTrack(final int trackIndex) throws IOException { public void selectTrack(final int trackIndex) throws IOException {
if (!parsed) { if (!parsed) {
throw new IllegalStateException("source must be parsed first"); throw new IllegalStateException("source must be parsed first");
} }
if (done) { if (done) {
throw new IOException("already done"); throw new IOException("already done");
} }
if (webmTrack != null) { if (webmTrack != null) {
throw new IOException("tracks already selected"); throw new IOException("tracks already selected");
} }
switch (webm.getAvailableTracks()[trackIndex].kind) { switch (webm.getAvailableTracks()[trackIndex].kind) {
case Audio: case Audio:
case Video: case Video:
break; break;
default: default:
throw new UnsupportedOperationException("the track must an audio or video stream"); throw new UnsupportedOperationException("the track must an audio or video stream");
} }
try { try {
webmTrack = webm.selectTrack(trackIndex); webmTrack = webm.selectTrack(trackIndex);
} finally { } finally {
parsed = true; parsed = true;
} }
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
done = true; done = true;
parsed = true; parsed = true;
webmTrack = null; webmTrack = null;
webm = null; webm = null;
if (!output.isClosed()) { if (!output.isClosed()) {
output.flush(); output.flush();
} }
source.close(); source.close();
output.close(); output.close();
} }
public void build() throws IOException { public void build() throws IOException {
float resolution; float resolution;
SimpleBlock bloq; SimpleBlock bloq;
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
ByteBuffer page = ByteBuffer.allocate(64 * 1024); ByteBuffer page = ByteBuffer.allocate(64 * 1024);
header.order(ByteOrder.LITTLE_ENDIAN); header.order(ByteOrder.LITTLE_ENDIAN);
/* step 1: get the amount of frames per seconds */ /* step 1: get the amount of frames per seconds */
switch (webmTrack.kind) { switch (webmTrack.kind) {
case Audio: case Audio:
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata); resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
if (resolution == 0f) { if (resolution == 0f) {
throw new RuntimeException("cannot get the audio sample rate"); throw new RuntimeException("cannot get the audio sample rate");
} }
break; break;
case Video: case Video:
// WARNING: untested // WARNING: untested
if (webmTrack.defaultDuration == 0) { if (webmTrack.defaultDuration == 0) {
throw new RuntimeException("missing default frame time"); throw new RuntimeException("missing default frame time");
} }
resolution = 1000f / ((float) webmTrack.defaultDuration resolution = 1000f / ((float) webmTrack.defaultDuration
/ webmSegment.info.timecodeScale); / webmSegment.info.timecodeScale);
break; break;
default: default:
throw new RuntimeException("not implemented"); throw new RuntimeException("not implemented");
} }
/* step 2: create packet with code init data */ /* step 2: create packet with code init data */
if (webmTrack.codecPrivate != null) { if (webmTrack.codecPrivate != null) {
addPacketSegment(webmTrack.codecPrivate.length); addPacketSegment(webmTrack.codecPrivate.length);
makePacketheader(0x00, header, webmTrack.codecPrivate); makePacketheader(0x00, header, webmTrack.codecPrivate);
write(header); write(header);
output.write(webmTrack.codecPrivate); output.write(webmTrack.codecPrivate);
} }
/* step 3: create packet with metadata */ /* step 3: create packet with metadata */
byte[] buffer = makeMetadata(); byte[] buffer = makeMetadata();
if (buffer != null) { if (buffer != null) {
addPacketSegment(buffer.length); addPacketSegment(buffer.length);
makePacketheader(0x00, header, buffer); makePacketheader(0x00, header, buffer);
write(header); write(header);
output.write(buffer); output.write(buffer);
} }
/* step 4: calculate amount of packets */ /* step 4: calculate amount of packets */
while (webmSegment != null) { while (webmSegment != null) {
bloq = getNextBlock(); bloq = getNextBlock();
if (bloq != null && addPacketSegment(bloq)) { if (bloq != null && addPacketSegment(bloq)) {
int pos = page.position(); int pos = page.position();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
bloq.data.read(page.array(), pos, bloq.dataSize); bloq.data.read(page.array(), pos, bloq.dataSize);
page.position(pos + bloq.dataSize); page.position(pos + bloq.dataSize);
continue; continue;
} }
// calculate the current packet duration using the next block // calculate the current packet duration using the next block
double elapsedNs = webmTrack.codecDelay; double elapsedNs = webmTrack.codecDelay;
if (bloq == null) { if (bloq == null) {
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
elapsedNs += webmBlockLastTimecode; elapsedNs += webmBlockLastTimecode;
if (webmTrack.defaultDuration > 0) { if (webmTrack.defaultDuration > 0) {
elapsedNs += webmTrack.defaultDuration; elapsedNs += webmTrack.defaultDuration;
} else { } else {
// hardcoded way, guess the sample duration // hardcoded way, guess the sample duration
elapsedNs += webmBlockNearDuration; elapsedNs += webmBlockNearDuration;
} }
} else { } else {
elapsedNs += bloq.absoluteTimeCodeNs; elapsedNs += bloq.absoluteTimeCodeNs;
} }
// get the sample count in the page // get the sample count in the page
elapsedNs = elapsedNs / TIME_SCALE_NS; elapsedNs = elapsedNs / TIME_SCALE_NS;
elapsedNs = Math.ceil(elapsedNs * resolution); elapsedNs = Math.ceil(elapsedNs * resolution);
// create header and calculate page checksum // create header and calculate page checksum
int checksum = makePacketheader((long) elapsedNs, header, null); int checksum = makePacketheader((long) elapsedNs, header, null);
checksum = calcCrc32(checksum, page.array(), page.position()); checksum = calcCrc32(checksum, page.array(), page.position());
header.putInt(HEADER_CHECKSUM_OFFSET, checksum); header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
// dump data // dump data
write(header); write(header);
write(page); write(page);
webmBlock = bloq; webmBlock = bloq;
} }
} }
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer, private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
final byte[] immediatePage) { final byte[] immediatePage) {
short length = HEADER_SIZE; short length = HEADER_SIZE;
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
buffer.put((byte) 0x00); // version buffer.put((byte) 0x00); // version
buffer.put(packetFlag); // type buffer.put(packetFlag); // type
buffer.putLong(granPos); // granulate position buffer.putLong(granPos); // granulate position
buffer.putInt(streamId); // bitstream serial number buffer.putInt(streamId); // bitstream serial number
buffer.putInt(sequenceCount++); // page sequence number buffer.putInt(sequenceCount++); // page sequence number
buffer.putInt(0x00); // page checksum buffer.putInt(0x00); // page checksum
buffer.put((byte) segmentTableSize); // segment table buffer.put((byte) segmentTableSize); // segment table
buffer.put(segmentTable, 0, segmentTableSize); // segment size buffer.put(segmentTable, 0, segmentTableSize); // segment size
length += segmentTableSize; length += segmentTableSize;
clearSegmentTable(); // clear segment table for next header clearSegmentTable(); // clear segment table for next header
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length); int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
if (immediatePage != null) { if (immediatePage != null) {
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length); checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32); buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
segmentTableNextTimestamp -= TIME_SCALE_NS; segmentTableNextTimestamp -= TIME_SCALE_NS;
} }
return checksumCrc32; return checksumCrc32;
} }
@Nullable @Nullable
private byte[] makeMetadata() { private byte[] makeMetadata() {
if ("A_OPUS".equals(webmTrack.codecId)) { if ("A_OPUS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
0x00, 0x00, 0x00, 0x00, // writing application string size (not present) 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
}; };
} else if ("A_VORBIS".equals(webmTrack.codecId)) { } else if ("A_VORBIS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x03, // ¿¿¿??? 0x03, // ¿¿¿???
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
0x00, 0x00, 0x00, 0x00, // writing application string size (not present) 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
}; };
} }
// not implemented for the desired codec // not implemented for the desired codec
return null; return null;
} }
private void write(final ByteBuffer buffer) throws IOException { private void write(final ByteBuffer buffer) throws IOException {
output.write(buffer.array(), 0, buffer.position()); output.write(buffer.array(), 0, buffer.position());
buffer.position(0); buffer.position(0);
} }
@Nullable @Nullable
private SimpleBlock getNextBlock() throws IOException { private SimpleBlock getNextBlock() throws IOException {
SimpleBlock res; SimpleBlock res;
if (webmBlock != null) { if (webmBlock != null) {
res = webmBlock; res = webmBlock;
webmBlock = null; webmBlock = null;
return res; return res;
} }
if (webmSegment == null) { if (webmSegment == null) {
webmSegment = webm.getNextSegment(); webmSegment = webm.getNextSegment();
if (webmSegment == null) { if (webmSegment == null) {
return null; // no more blocks in the selected track return null; // no more blocks in the selected track
} }
} }
if (webmCluster == null) { if (webmCluster == null) {
webmCluster = webmSegment.getNextCluster(); webmCluster = webmSegment.getNextCluster();
if (webmCluster == null) { if (webmCluster == null) {
webmSegment = null; webmSegment = null;
return getNextBlock(); return getNextBlock();
} }
} }
res = webmCluster.getNextSimpleBlock(); res = webmCluster.getNextSimpleBlock();
if (res == null) { if (res == null) {
webmCluster = null; webmCluster = null;
return getNextBlock(); return getNextBlock();
} }
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode; webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
webmBlockLastTimecode = res.absoluteTimeCodeNs; webmBlockLastTimecode = res.absoluteTimeCodeNs;
return res; return res;
} }
private float getSampleFrequencyFromTrack(final byte[] bMetadata) { private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
// hardcoded way // hardcoded way
ByteBuffer buffer = ByteBuffer.wrap(bMetadata); ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
while (buffer.remaining() >= 6) { while (buffer.remaining() >= 6) {
int id = buffer.getShort() & 0xFFFF; int id = buffer.getShort() & 0xFFFF;
if (id == 0x0000B584) { if (id == 0x0000B584) {
return buffer.getFloat(); return buffer.getFloat();
} }
} }
return 0f; return 0.0f;
} }
private void clearSegmentTable() { private void clearSegmentTable() {
segmentTableNextTimestamp += TIME_SCALE_NS; segmentTableNextTimestamp += TIME_SCALE_NS;
packetFlag = FLAG_UNSET; packetFlag = FLAG_UNSET;
segmentTableSize = 0; segmentTableSize = 0;
} }
private boolean addPacketSegment(final SimpleBlock block) { private boolean addPacketSegment(final SimpleBlock block) {
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay; long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
if (timestamp >= segmentTableNextTimestamp) { if (timestamp >= segmentTableNextTimestamp) {
return false; return false;
} }
return addPacketSegment(block.dataSize); return addPacketSegment(block.dataSize);
} }
private boolean addPacketSegment(final int size) { private boolean addPacketSegment(final int size) {
if (size > 65025) { if (size > 65025) {
throw new UnsupportedOperationException("page size cannot be larger than 65025"); throw new UnsupportedOperationException("page size cannot be larger than 65025");
} }
int available = (segmentTable.length - segmentTableSize) * 255; int available = (segmentTable.length - segmentTableSize) * 255;
boolean extra = (size % 255) == 0; boolean extra = (size % 255) == 0;
if (extra) { if (extra) {
// add a zero byte entry in the table // add a zero byte entry in the table
// required to indicate the sample size is multiple of 255 // required to indicate the sample size is multiple of 255
available -= 255; available -= 255;
} }
// check if possible add the segment, without overflow the table // check if possible add the segment, without overflow the table
if (available < size) { if (available < size) {
return false; // not enough space on the page return false; // not enough space on the page
} }
for (int seg = size; seg > 0; seg -= 255) { for (int seg = size; seg > 0; seg -= 255) {
segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255); segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
} }
if (extra) { if (extra) {
segmentTable[segmentTableSize++] = 0x00; segmentTable[segmentTableSize++] = 0x00;
} }
return true; return true;
} }
private void populateCrc32Table() { private void populateCrc32Table() {
for (int i = 0; i < 0x100; i++) { for (int i = 0; i < 0x100; i++) {
int crc = i << 24; int crc = i << 24;
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
long b = crc >>> 31; long b = crc >>> 31;
crc <<= 1; crc <<= 1;
crc ^= (int) (0x100000000L - b) & 0x04c11db7; crc ^= (int) (0x100000000L - b) & 0x04c11db7;
} }
crc32Table[i] = crc; crc32Table[i] = crc;
} }
} }
private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) { private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
int crc = initialCrc; int crc = initialCrc;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
int reg = (crc >>> 24) & 0xff; int reg = (crc >>> 24) & 0xff;
crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
} }
return crc; return crc;
} }
} }

View file

@ -8,22 +8,23 @@ import android.os.BatteryManager;
import android.os.Build; import android.os.Build;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.NonNull;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
import static android.content.Context.BATTERY_SERVICE; import static android.content.Context.BATTERY_SERVICE;
import static android.content.Context.UI_MODE_SERVICE; import static android.content.Context.UI_MODE_SERVICE;
public final class AndroidTvUtils { public final class DeviceUtils {
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
private static Boolean isTV = null; private static Boolean isTV = null;
private AndroidTvUtils() { private DeviceUtils() {
} }
public static boolean isTv(final Context context) { public static boolean isTv(final Context context) {
if (AndroidTvUtils.isTV != null) { if (isTV != null) {
return AndroidTvUtils.isTV; return isTV;
} }
PackageManager pm = App.getApp().getPackageManager(); PackageManager pm = App.getApp().getPackageManager();
@ -48,8 +49,15 @@ public final class AndroidTvUtils {
isTv = isTv || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); isTv = isTv || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
} }
AndroidTvUtils.isTV = isTv; DeviceUtils.isTV = isTv;
return AndroidTvUtils.isTV; return DeviceUtils.isTV;
}
public static boolean isTablet(@NonNull final Context context) {
return (context
.getResources()
.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
} }
public static boolean isConfirmKey(final int keyCode) { public static boolean isConfirmKey(final int keyCode) {

View file

@ -536,7 +536,7 @@ public final class ListHelper {
* @param context App context * @param context App context
* @return {@code true} if connected to a metered network * @return {@code true} if connected to a metered network
*/ */
private static boolean isMeteredNetwork(final Context context) { public static boolean isMeteredNetwork(final Context context) {
ConnectivityManager manager ConnectivityManager manager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (manager == null || manager.getActiveNetworkInfo() == null) { if (manager == null || manager.getActiveNetworkInfo() == null) {

View file

@ -13,6 +13,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -46,14 +47,12 @@ import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment;
import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.BackgroundPlayerActivity; import org.schabi.newpipe.player.BackgroundPlayerActivity;
import org.schabi.newpipe.player.BasePlayer; import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayerActivity;
import org.schabi.newpipe.player.VideoPlayer; import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList; import java.util.ArrayList;
@ -85,6 +84,7 @@ public final class NavigationHelper {
intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality); intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
} }
intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback); intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_VIDEO);
return intent; return intent;
} }
@ -112,11 +112,13 @@ public final class NavigationHelper {
public static Intent getPlayerIntent(@NonNull final Context context, public static Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class targetClazz, @NonNull final Class targetClazz,
@NonNull final PlayQueue playQueue, @NonNull final PlayQueue playQueue,
final int repeatMode, final float playbackSpeed, final int repeatMode,
final float playbackSpeed,
final float playbackPitch, final float playbackPitch,
final boolean playbackSkipSilence, final boolean playbackSkipSilence,
@Nullable final String playbackQuality, @Nullable final String playbackQuality,
final boolean resumePlayback, final boolean startPaused, final boolean resumePlayback,
final boolean startPaused,
final boolean isMuted) { final boolean isMuted) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode) .putExtra(BasePlayer.REPEAT_MODE, repeatMode)
@ -124,12 +126,42 @@ public final class NavigationHelper {
.putExtra(BasePlayer.IS_MUTED, isMuted); .putExtra(BasePlayer.IS_MUTED, isMuted);
} }
public static void playOnMainPlayer(final Context context, final PlayQueue queue, public static void playOnMainPlayer(
final AppCompatActivity activity,
final PlayQueue queue,
final boolean autoPlay) {
playOnMainPlayer(activity.getSupportFragmentManager(), queue, autoPlay);
}
public static void playOnMainPlayer(
final FragmentManager fragmentManager,
final PlayQueue queue,
final boolean autoPlay) {
final PlayQueueItem currentStream = queue.getItem();
openVideoDetailFragment(
fragmentManager,
currentStream.getServiceId(),
currentStream.getUrl(),
currentStream.getTitle(),
autoPlay,
queue);
}
public static void playOnMainPlayer(@NonNull final Context context,
@NonNull final PlayQueue queue,
@NonNull final StreamingService.LinkType linkType,
@NonNull final String url,
@NonNull final String title,
final boolean autoPlay,
final boolean resumePlayback) { final boolean resumePlayback) {
final Intent playerIntent
= getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback); final Intent intent = getPlayerIntent(context, MainActivity.class, queue, resumePlayback);
playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(playerIntent); intent.putExtra(Constants.KEY_LINK_TYPE, linkType);
intent.putExtra(Constants.KEY_URL, url);
intent.putExtra(Constants.KEY_TITLE, title);
intent.putExtra(VideoDetailFragment.AUTO_PLAY, autoPlay);
context.startActivity(intent);
} }
public static void playOnPopupPlayer(final Context context, final PlayQueue queue, public static void playOnPopupPlayer(final Context context, final PlayQueue queue,
@ -140,16 +172,19 @@ public final class NavigationHelper {
} }
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
startService(context, final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback)); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
startService(context, intent);
} }
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, public static void playOnBackgroundPlayer(final Context context,
final PlayQueue queue,
final boolean resumePlayback) { final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
.show(); .show();
startService(context, final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback)); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
startService(context, intent);
} }
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue,
@ -166,8 +201,10 @@ public final class NavigationHelper {
} }
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
startService(context, getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, final Intent intent = getPlayerEnqueueIntent(
selectOnAppend, resumePlayback)); context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
startService(context, intent);
} }
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue,
@ -175,12 +212,15 @@ public final class NavigationHelper {
enqueueOnBackgroundPlayer(context, queue, false, resumePlayback); enqueueOnBackgroundPlayer(context, queue, false, resumePlayback);
} }
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, public static void enqueueOnBackgroundPlayer(final Context context,
final PlayQueue queue,
final boolean selectOnAppend, final boolean selectOnAppend,
final boolean resumePlayback) { final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
startService(context, getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, final Intent intent = getPlayerEnqueueIntent(
selectOnAppend, resumePlayback)); context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
startService(context, intent);
} }
public static void startService(@NonNull final Context context, @NonNull final Intent intent) { public static void startService(@NonNull final Context context, @NonNull final Intent intent) {
@ -311,31 +351,43 @@ public final class NavigationHelper {
public static void openVideoDetailFragment(final FragmentManager fragmentManager, public static void openVideoDetailFragment(final FragmentManager fragmentManager,
final int serviceId, final String url, final int serviceId, final String url,
final String title) { final String title) {
openVideoDetailFragment(fragmentManager, serviceId, url, title, false); openVideoDetailFragment(fragmentManager, serviceId, url, title, true, null);
} }
public static void openVideoDetailFragment(final FragmentManager fragmentManager, public static void openVideoDetailFragment(
final int serviceId, final String url, final FragmentManager fragmentManager,
final String name, final boolean autoPlay) { final int serviceId,
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_holder); final String url,
final String title,
final boolean autoPlay,
final PlayQueue playQueue) {
final Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) { if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
VideoDetailFragment detailFragment = (VideoDetailFragment) fragment; expandMainPlayer(fragment.requireActivity());
final VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
detailFragment.setAutoplay(autoPlay); detailFragment.setAutoplay(autoPlay);
detailFragment.selectAndLoadVideo(serviceId, url, name == null ? "" : name); detailFragment
.selectAndLoadVideo(serviceId, url, title == null ? "" : title, playQueue);
detailFragment.scrollToTop();
return; return;
} }
VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, final VideoDetailFragment instance = VideoDetailFragment
name == null ? "" : name); .getInstance(serviceId, url, title == null ? "" : title, playQueue);
instance.setAutoplay(autoPlay); instance.setAutoplay(autoPlay);
defaultTransaction(fragmentManager) defaultTransaction(fragmentManager)
.replace(R.id.fragment_holder, instance) .replace(R.id.fragment_player_holder, instance)
.addToBackStack(null) .runOnCommit(() -> expandMainPlayer(instance.requireActivity()))
.commit(); .commit();
} }
public static void expandMainPlayer(final Context context) {
final Intent intent = new Intent(VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER);
context.sendBroadcast(intent);
}
public static void openChannelFragment(final FragmentManager fragmentManager, public static void openChannelFragment(final FragmentManager fragmentManager,
final int serviceId, final String url, final int serviceId, final String url,
final String name) { final String name) {
@ -505,10 +557,6 @@ public final class NavigationHelper {
return getServicePlayerActivityIntent(context, BackgroundPlayerActivity.class); return getServicePlayerActivityIntent(context, BackgroundPlayerActivity.class);
} }
public static Intent getPopupPlayerActivityIntent(final Context context) {
return getServicePlayerActivityIntent(context, PopupVideoPlayerActivity.class);
}
private static Intent getServicePlayerActivityIntent(final Context context, private static Intent getServicePlayerActivityIntent(final Context context,
final Class activityClass) { final Class activityClass) {
Intent intent = new Intent(context, activityClass); Intent intent = new Intent(context, activityClass);

View file

@ -31,8 +31,9 @@ public final class ShareUtils {
// no browser set as default // no browser set as default
openInDefaultApp(context, url); openInDefaultApp(context, url);
} else { } else {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.setPackage(defaultBrowserPackageName); .setPackage(defaultBrowserPackageName)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
} }
} }
@ -48,7 +49,8 @@ public final class ShareUtils {
private static void openInDefaultApp(final Context context, final String url) { private static void openInDefaultApp(final Context context, final String url) {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(Intent.createChooser( context.startActivity(Intent.createChooser(
intent, context.getString(R.string.share_dialog_title))); intent, context.getString(R.string.share_dialog_title))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} }
/** /**
@ -60,7 +62,8 @@ public final class ShareUtils {
* @return the package name of the default browser, or "android" if there's no default * @return the package name of the default browser, or "android" if there's no default
*/ */
private static String getDefaultBrowserPackageName(final Context context) { private static String getDefaultBrowserPackageName(final Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity( final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
intent, PackageManager.MATCH_DEFAULT_ONLY); intent, PackageManager.MATCH_DEFAULT_ONLY);
return resolveInfo.activityInfo.packageName; return resolveInfo.activityInfo.packageName;
@ -79,7 +82,8 @@ public final class ShareUtils {
intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url); intent.putExtra(Intent.EXTRA_TEXT, url);
context.startActivity(Intent.createChooser( context.startActivity(Intent.createChooser(
intent, context.getString(R.string.share_dialog_title))); intent, context.getString(R.string.share_dialog_title))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} }
/** /**

View file

@ -0,0 +1,114 @@
package org.schabi.newpipe.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
public class ExpandableSurfaceView extends SurfaceView {
private int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
private int baseHeight = 0;
private int maxHeight = 0;
private float videoAspectRatio = 0.0f;
private float scaleX = 1.0f;
private float scaleY = 1.0f;
public ExpandableSurfaceView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (videoAspectRatio == 0.0f) {
return;
}
int width = MeasureSpec.getSize(widthMeasureSpec);
final boolean verticalVideo = videoAspectRatio < 1;
// Use maxHeight only on non-fit resize mode and in vertical videos
int height = maxHeight != 0
&& resizeMode != AspectRatioFrameLayout.RESIZE_MODE_FIT
&& verticalVideo ? maxHeight : baseHeight;
if (height == 0) {
return;
}
final float viewAspectRatio = width / ((float) height);
final float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
scaleX = 1.0f;
scaleY = 1.0f;
switch (resizeMode) {
case AspectRatioFrameLayout.RESIZE_MODE_FIT:
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
break;
case RESIZE_MODE_ZOOM:
if (aspectDeformation < 0) {
scaleY = viewAspectRatio / videoAspectRatio;
} else {
scaleX = videoAspectRatio / viewAspectRatio;
}
break;
default:
break;
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
/**
* Scale view only in {@link #onLayout} to make transition for ZOOM mode as smooth as possible.
*/
@Override
protected void onLayout(final boolean changed,
final int left, final int top, final int right, final int bottom) {
setScaleX(scaleX);
setScaleY(scaleY);
}
/**
* @param base The height that will be used in every resize mode as a minimum height
* @param max The max height for vertical videos in non-FIT resize modes
*/
public void setHeights(final int base, final int max) {
if (baseHeight == base && maxHeight == max) {
return;
}
baseHeight = base;
maxHeight = max;
requestLayout();
}
public void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int newResizeMode) {
if (resizeMode == newResizeMode) {
return;
}
resizeMode = newResizeMode;
requestLayout();
}
@AspectRatioFrameLayout.ResizeMode
public int getResizeMode() {
return resizeMode;
}
public void setAspectRatio(final float aspectRatio) {
if (videoAspectRatio == aspectRatio) {
return;
}
videoAspectRatio = aspectRatio;
requestLayout();
}
}

View file

@ -24,7 +24,7 @@ import android.view.KeyEvent;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.SeekBar; import android.widget.SeekBar;
import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.DeviceUtils;
/** /**
* SeekBar, adapted for directional navigation. It emulates touch-related callbacks * SeekBar, adapted for directional navigation. It emulates touch-related callbacks
@ -58,7 +58,7 @@ public final class FocusAwareSeekBar extends MarkableSeekBar {
@Override @Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) { public boolean onKeyDown(final int keyCode, final KeyEvent event) {
if (!isInTouchMode() && AndroidTvUtils.isConfirmKey(keyCode)) { if (!isInTouchMode() && DeviceUtils.isConfirmKey(keyCode)) {
releaseTrack(); releaseTrack();
} }

View file

@ -38,6 +38,7 @@ import android.view.ViewTreeObserver;
import android.view.Window; import android.view.Window;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.view.WindowCallbackWrapper; import androidx.appcompat.view.WindowCallbackWrapper;
@ -95,7 +96,9 @@ public final class FocusOverlayView extends Drawable implements
if (focusedView != null && isShown(focusedView)) { if (focusedView != null && isShown(focusedView)) {
focusedView.getGlobalVisibleRect(focusRect); focusedView.getGlobalVisibleRect(focusRect);
} else { }
if (shouldClearFocusRect(focusedView, focusRect)) {
focusRect.setEmpty(); focusRect.setEmpty();
} }
@ -170,6 +173,16 @@ public final class FocusOverlayView extends Drawable implements
public void setColorFilter(final ColorFilter colorFilter) { public void setColorFilter(final ColorFilter colorFilter) {
} }
/*
* When any view in the player looses it's focus (after setVisibility(GONE)) the focus gets
* added to the whole fragment which has a width and height equal to the window frame.
* The easiest way to avoid the unneeded frame is to skip highlighting of rect that is
* equal to the overlayView bounds
* */
private boolean shouldClearFocusRect(@Nullable final View focusedView, final Rect focusedRect) {
return focusedView == null || focusedRect.equals(getBounds());
}
public static void setupFocusObserver(final Dialog dialog) { public static void setupFocusObserver(final Dialog dialog) {
Rect displayRect = new Rect(); Rect displayRect = new Rect();

View file

@ -89,7 +89,7 @@ public abstract class Postprocessing implements Serializable {
} }
public void setTemporalDir(@NonNull File directory) { public void setTemporalDir(@NonNull File directory) {
long rnd = (int) (Math.random() * 100000f); long rnd = (int) (Math.random() * 100000.0f);
tempFile = new File(directory, rnd + "_" + System.nanoTime() + ".tmp"); tempFile = new File(directory, rnd + "_" + System.nanoTime() + ".tmp");
} }

View file

@ -210,7 +210,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
} else { } else {
h.progress.setMarquee(false); h.progress.setMarquee(false);
h.status.setText("100%"); h.status.setText("100%");
h.progress.setProgress(1f); h.progress.setProgress(1.0f);
h.size.setText(Utility.formatBytes(item.mission.length)); h.size.setText(Utility.formatBytes(item.mission.length));
} }
} }
@ -243,7 +243,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
double progress; double progress;
if (mission.unknownLength) { if (mission.unknownLength) {
progress = Double.NaN; progress = Double.NaN;
h.progress.setProgress(0f); h.progress.setProgress(0.0f);
} else { } else {
progress = done / length; progress = done / length;
} }
@ -310,7 +310,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
for (int i = 0; i < h.lastSpeed.length; i++) { for (int i = 0; i < h.lastSpeed.length; i++) {
averageSpeed += h.lastSpeed[i]; averageSpeed += h.lastSpeed[i];
} }
averageSpeed /= h.lastSpeed.length + 1f; averageSpeed /= h.lastSpeed.length + 1.0f;
} }
String speedStr = Utility.formatSpeed(averageSpeed); String speedStr = Utility.formatSpeed(averageSpeed);

View file

@ -26,7 +26,7 @@ public class ProgressDrawable extends Drawable {
public ProgressDrawable() { public ProgressDrawable() {
mMarqueeLine = null;// marquee disabled mMarqueeLine = null;// marquee disabled
mMarqueeProgress = 0f; mMarqueeProgress = 0.0f;
mMarqueeSize = 0; mMarqueeSize = 0;
mMarqueeNext = 0; mMarqueeNext = 0;
} }
@ -122,7 +122,7 @@ public class ProgressDrawable extends Drawable {
} }
private void setupMarquee(int width, int height) { private void setupMarquee(int width, int height) {
mMarqueeSize = (int) ((width * 10f) / 100f);// the size is 10% of the width mMarqueeSize = (int) ((width * 10.0f) / 100.0f);// the size is 10% of the width
mMarqueeLine.rewind(); mMarqueeLine.rewind();
mMarqueeLine.moveTo(-mMarqueeSize, -mMarqueeSize); mMarqueeLine.moveTo(-mMarqueeSize, -mMarqueeSize);

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#ffffff">
<!-- Tint here is for preventing pixelization -->
<path android:fillColor="#ffffff"
android:pathData="M5,19 L15,12 5,5ZM16,5v14h3v-14z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#ffffff">
<!-- Tint here is for preventing pixelization -->
<path android:fillColor="#ffffff"
android:pathData="m19,5 l-10,7 10,7zM8,19v-14H5v14z"/>
</vector>

View file

@ -185,7 +185,7 @@
android:orientation="horizontal" android:orientation="horizontal"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/control_backward" android:id="@+id/control_backward"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
@ -198,8 +198,8 @@
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitCenter"
android:src="@drawable/exo_controls_previous" app:srcCompat="@drawable/ic_previous_white_24dp"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@ -238,7 +238,7 @@
app:srcCompat="@drawable/ic_shuffle_white_24dp" app:srcCompat="@drawable/ic_shuffle_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/control_forward" android:id="@+id/control_forward"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
@ -251,8 +251,8 @@
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitCenter"
android:src="@drawable/exo_controls_next" app:srcCompat="@drawable/ic_next_white_24dp"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/windowBackground">
<LinearLayout
android:id="@+id/video_item_detail" android:id="@+id/video_item_detail"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -42,6 +47,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground"
android:descendantFocusability="afterDescendants"
app:layout_collapseMode="parallax"> app:layout_collapseMode="parallax">
<ImageView <ImageView
@ -146,6 +152,14 @@
tools:progress="40" tools:progress="40"
tools:visibility="visible" /> tools:visibility="visible" />
<!-- Player will be inserted here in realtime -->
<FrameLayout
android:id="@+id/player_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
/>
</FrameLayout> </FrameLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
@ -155,7 +169,6 @@
android:id="@+id/detail_content_root_layout" android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll"> app:layout_scrollFlags="scroll">
<!-- TITLE --> <!-- TITLE -->
@ -555,25 +568,22 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager" android:id="@+id/viewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom|center"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp"/>
<com.google.android.material.tabs.TabLayout </androidx.viewpager.widget.ViewPager>
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
</com.google.android.material.tabs.TabLayout>
</org.schabi.newpipe.views.FocusAwareCoordinator> </org.schabi.newpipe.views.FocusAwareCoordinator>
@ -586,3 +596,105 @@
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
<RelativeLayout
android:id="@+id/overlay_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.9"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"
android:descendantFocusability="blocksDescendants"
android:background="?attr/windowBackground" >
<ImageButton
android:id="@+id/overlay_thumbnail"
android:layout_width="50dp"
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:scaleType="fitCenter"
android:gravity="center_vertical"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/dummy_thumbnail"
android:background="@color/transparent_background_color"/>
<LinearLayout
android:id="@+id/overlay_metadata_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"
android:clickable="true"
android:focusable="true"
android:layout_toEndOf="@+id/overlay_thumbnail"
android:layout_toStartOf="@+id/overlay_buttons_layout"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/overlay_title_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONVideo Title LONG very LONG"/>
<TextView
android:id="@+id/overlay_channel_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="The Video Artist LONG very LONG very Long"/>
</LinearLayout>
<LinearLayout
android:id="@+id/overlay_buttons_layout"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:gravity="center_vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:layout_alignParentEnd="true"
tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/overlay_play_pause_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:padding="10dp"
android:scaleType="center"
app:srcCompat="?attr/ic_play_arrow"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<ImageButton
android:id="@+id/overlay_close_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginLeft="2dp"
android:padding="10dp"
android:scaleType="center"
app:srcCompat="?attr/ic_close"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout>
</RelativeLayout>
</FrameLayout>

View file

@ -2,32 +2,24 @@
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@color/black"
android:gravity="center"> android:gravity="center">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout <org.schabi.newpipe.views.ExpandableSurfaceView
android:id="@+id/aspectRatioLayout" android:id="@+id/surfaceView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true" android:layout_centerInParent="true"/>
android:layout_gravity="center">
<SurfaceView <View
android:id="@+id/surfaceView" android:id="@+id/surfaceForeground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"/> android:background="@android:color/black"
android:layout_alignBottom="@+id/surfaceView"/>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<com.google.android.exoplayer2.ui.SubtitleView <com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView" android:id="@+id/subtitleView"
@ -46,85 +38,6 @@
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:visibility="visible"/> tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="380dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:visibility="invisible"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="40dp"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<ImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playbackControlRoot" android:id="@+id/playbackControlRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -133,37 +46,72 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<View
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/player_controls_top_background"
android:layout_alignParentTop="true" />
<View
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/player_controls_background"
android:layout_alignParentBottom="true" />
<!-- All top controls in this layout -->
<RelativeLayout <RelativeLayout
android:id="@+id/playbackWindowRoot" android:id="@+id/playbackWindowRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<RelativeLayout <LinearLayout
android:id="@+id/topControls" android:id="@+id/topControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:background="@drawable/player_controls_top_background" android:orientation="vertical"
android:gravity="top"
android:descendantFocusability="afterDescendants"
android:paddingTop="@dimen/player_main_top_padding"
android:paddingStart="@dimen/player_main_controls_padding"
android:paddingEnd="@dimen/player_main_controls_padding"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/primaryControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="45dp"
android:baselineAligned="false"
android:descendantFocusability="afterDescendants"
android:gravity="top" android:gravity="top"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playerCloseButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
app:srcCompat="?attr/ic_close"
android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone" />
<LinearLayout <LinearLayout
android:id="@+id/metadataView" android:id="@+id/metadataView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/qualityTextView"
android:gravity="top" android:gravity="top"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="8dp" android:layout_marginTop="6dp"
android:paddingRight="8dp" android:layout_marginRight="8dp"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded"
android:layout_weight="1">
<TextView <TextView
android:id="@+id/titleTextView" android:id="@+id/titleTextView"
@ -177,7 +125,6 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
android:clickable="true"
tools:ignore="RtlHardcoded" tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONG"/> tools:text="The Video Title LONG very LONG"/>
@ -192,94 +139,86 @@
android:singleLine="true" android:singleLine="true"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="12sp" android:textSize="12sp"
android:clickable="true"
tools:text="The Video Artist LONG very LONG very Long"/> tools:text="The Video Artist LONG very LONG very Long"/>
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/qualityTextView" android:id="@+id/qualityTextView"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="2dp" android:minWidth="0dp"
android:layout_marginRight="2dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_toLeftOf="@+id/playbackSpeed" android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:minWidth="50dp"
android:text="720p" android:text="720p"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:textAllCaps="false" android:textAllCaps="false"
android:padding="5dp" android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/> tools:ignore="HardcodedText,RtlHardcoded"/>
<Button <Button
android:id="@+id/playbackSpeed" android:id="@+id/playbackSpeed"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="35dp"
android:layout_marginRight="2dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_toLeftOf="@+id/queueButton" android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:minHeight="35dp" android:minWidth="0dp"
android:minWidth="40dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:textAllCaps="false" android:textAllCaps="false"
android:padding="5dp" android:background="?attr/selectableItemBackground"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="1x" /> tools:text="1x"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/queueButton" android:id="@+id/queueButton"
android:layout_width="30dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="2dp" android:paddingTop="5dp"
android:layout_marginRight="2dp" android:paddingStart="3dp"
android:layout_toLeftOf="@+id/moreOptionsButton" android:paddingEnd="3dp"
android:paddingBottom="3dp"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp" android:scaleType="fitCenter"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_list_white_24dp" app:srcCompat="@drawable/ic_list_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/> tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone"
tools:visibility="visible"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moreOptionsButton" android:id="@+id/moreOptionsButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginLeft="2dp"
android:padding="5dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_expand_more_white_24dp" app:srcCompat="@drawable/ic_expand_more_white_24dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription,RtlHardcoded"/> tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<RelativeLayout </LinearLayout>
<LinearLayout
android:id="@+id/secondaryControls" android:id="@+id/secondaryControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/topControls"
android:gravity="top" android:gravity="top"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:visibility="invisible" android:visibility="invisible"
tools:ignore="RtlHardcoded" tools:ignore="RtlHardcoded"
tools:visibility="visible"> tools:visibility="visible">
<Button <Button
android:id="@+id/resizeTextView" android:id="@+id/resizeTextView"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="8dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:layout_alignParentLeft="true"
android:gravity="center" android:gravity="center"
android:minWidth="50dp" android:minWidth="50dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
@ -290,149 +229,120 @@
tools:text="FIT"/> tools:text="FIT"/>
<Button <Button
style="@style/Widget.AppCompat.Button.Borderless"
android:id="@+id/captionTextView" android:id="@+id/captionTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:layout_toLeftOf="@id/switchMute"
android:layout_toRightOf="@id/resizeTextView"
android:gravity="center|left" android:gravity="center|left"
android:minHeight="35dp" android:minHeight="35dp"
android:minWidth="40dp" android:lines="1"
android:paddingLeft="2dp" android:ellipsize="end"
android:paddingRight="2dp" android:minWidth="50dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:textAllCaps="false" android:textAllCaps="false"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="RelativeOverlap,RtlHardcoded" tools:ignore="RelativeOverlap,RtlHardcoded"
tools:text="English" /> tools:text="English"/>
<ImageButton <Space
android:id="@+id/kodi" android:id="@+id/spaceBeforeButton"
android:layout_width="30dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="0dp"
android:layout_marginLeft="4dp" android:layout_weight="3"/>
android:layout_marginRight="2dp"
android:layout_alignParentRight="true" <androidx.appcompat.widget.AppCompatImageButton
android:layout_centerVertical="true" android:id="@+id/playWithKodi"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_cast_white_24dp" app:srcCompat="@drawable/ic_cast_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/play_with_kodi_title" android:contentDescription="@string/play_with_kodi_title"
android:visibility="gone" tools:ignore="RtlHardcoded"/>
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/share" android:id="@+id/openInBrowser"
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="35dp"
android:layout_marginLeft="4dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="2dp" android:layout_marginEnd="8dp"
android:layout_toLeftOf="@id/kodi" android:clickable="true"
android:layout_alignWithParentIfMissing="true" android:focusable="true"
android:layout_centerVertical="true" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_language_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/open_in_browser"
tools:ignore="RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_share_white_24dp" app:srcCompat="@drawable/ic_share_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/share" android:contentDescription="@string/share"
tools:ignore="RtlHardcoded"/> tools:ignore="RtlHardcoded"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/toggleOrientation"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/share"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_screen_rotation_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/toggle_orientation"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchPopup"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/toggleOrientation"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_fullscreen_exit_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_popup"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchBackground"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/switchPopup"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_headset_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_background"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchMute" android:id="@+id/switchMute"
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="37dp"
android:layout_marginLeft="4dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/switchBackground"
android:layout_centerVertical="true"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_volume_off_white_24dp" app:srcCompat="@drawable/ic_volume_off_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_background" android:contentDescription="@string/mute"
tools:ignore="RtlHardcoded" /> tools:ignore="RtlHardcoded" />
</RelativeLayout> </LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/fullScreenButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout <LinearLayout
android:id="@+id/bottomControls" android:id="@+id/bottomControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="40dp"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:background="@drawable/player_controls_background"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingLeft="16dp" android:paddingLeft="@dimen/player_main_controls_padding"
android:paddingRight="16dp"> android:paddingRight="@dimen/player_main_controls_padding">
<TextView <TextView
android:id="@+id/playbackCurrentTime" android:id="@+id/playbackCurrentTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:minHeight="40dp" android:minHeight="30dp"
android:text="-:--:--" android:text="-:--:--"
android:textColor="@android:color/white" android:textColor="@android:color/white"
tools:ignore="HardcodedText" tools:ignore="HardcodedText"
@ -448,6 +358,7 @@
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:paddingTop="8dp" android:paddingTop="8dp"
tools:progress="25" tools:progress="25"
android:nextFocusDown="@id/screenRotationButton"
tools:secondaryProgress="50"/> tools:secondaryProgress="50"/>
<TextView <TextView
@ -473,67 +384,147 @@
android:visibility="gone" android:visibility="gone"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" /> tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/screenRotationButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:padding="@dimen/player_main_buttons_padding"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"
android:nextFocusUp="@id/playbackSeekBar"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
<ImageButton <LinearLayout
android:id="@+id/playPauseButton" android:layout_width="match_parent"
android:layout_width="100dp" android:layout_height="match_parent"
android:layout_height="100dp" android:gravity="center"
android:layout_centerInParent="true" android:orientation="horizontal"
android:weightSum="5.5">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPreviousButton"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:layout_marginEnd="10dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_previous_white_24dp"
tools:ignore="ContentDescription"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPauseButton"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_pause_white_24dp" app:srcCompat="@drawable/ic_pause_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPreviousButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:layout_centerInParent="true"
android:layout_toStartOf="@id/playPauseButton"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/playNextButton" android:id="@+id/playNextButton"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_marginStart="30dp" android:layout_weight="1"
android:layout_centerInParent="true" android:layout_marginStart="10dp"
android:layout_toEndOf="@id/playPauseButton"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitCenter"
android:src="@drawable/exo_controls_next" app:srcCompat="@drawable/ic_next_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<Button </LinearLayout>
android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/playPauseButton"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="invisible" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="380dp"
android:layout_alignParentEnd="true"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -573,10 +564,7 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_toEndOf="@+id/loading_panel"
android:layout_toRightOf="@+id/loading_panel"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<RelativeLayout <RelativeLayout
@ -651,4 +639,41 @@
tools:visibility="visible" /> tools:visibility="visible" />
</RelativeLayout> </RelativeLayout>
<TextView
android:id="@+id/resizing_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:background="#6e000000"
android:gravity="center"
android:padding="5dp"
android:text="@string/popup_resizing_indicator_title"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="gone" />
<View
android:id="@+id/closingOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AAFF0000"
android:visibility="gone" />
<Button
android:id="@+id/closeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="gone" />
</RelativeLayout> </RelativeLayout>

View file

@ -100,6 +100,7 @@
android:id="@+id/errorView" android:id="@+id/errorView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textIsSelectable="true"
android:typeface="monospace"/> android:typeface="monospace"/>
</HorizontalScrollView> </HorizontalScrollView>

View file

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.schabi.newpipe.views.FocusAwareDrawerLayout <org.schabi.newpipe.views.FocusAwareDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<org.schabi.newpipe.views.FocusAwareCoordinator
<FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical"
tools:context="org.schabi.newpipe.MainActivity">
<FrameLayout <FrameLayout
android:id="@+id/fragment_holder" android:id="@+id/fragment_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -20,7 +18,18 @@
android:layout_marginTop="?attr/actionBarSize" /> android:layout_marginTop="?attr/actionBarSize" />
<include layout="@layout/toolbar_layout" /> <include layout="@layout/toolbar_layout" />
</FrameLayout>
<FrameLayout
android:id="@+id/fragment_player_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="org.schabi.newpipe.player.event.CustomBottomSheetBehavior">
</FrameLayout>
</org.schabi.newpipe.views.FocusAwareCoordinator>
<include layout="@layout/drawer_layout"/> <include layout="@layout/drawer_layout"/>

View file

@ -184,10 +184,10 @@
app:srcCompat="@drawable/ic_repeat_white_24dp" app:srcCompat="@drawable/ic_repeat_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/control_backward" android:id="@+id/control_backward"
android:layout_width="40dp" android:layout_width="32dp"
android:layout_height="40dp" android:layout_height="32dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="5dp" android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_fast_rewind" android:layout_toLeftOf="@+id/control_fast_rewind"
@ -196,7 +196,7 @@
android:focusable="true" android:focusable="true"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_previous" app:srcCompat="@drawable/ic_previous_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
@ -263,10 +263,10 @@
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_fastforward"/> android:src="@drawable/exo_controls_fastforward"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/control_forward" android:id="@+id/control_forward"
android:layout_width="40dp" android:layout_width="32dp"
android:layout_height="40dp" android:layout_height="32dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginRight="5dp" android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_fast_forward" android:layout_toRightOf="@+id/control_fast_forward"
@ -275,7 +275,7 @@
android:focusable="true" android:focusable="true"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_next" app:srcCompat="@drawable/ic_next_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <ImageButton

View file

@ -5,13 +5,13 @@
android:id="@+id/video_item_detail" android:id="@+id/video_item_detail"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/windowBackground"
android:focusableInTouchMode="true"> android:focusableInTouchMode="true">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/detail_main_content" android:id="@+id/detail_main_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarlayout" android:id="@+id/appbarlayout"
@ -140,6 +140,13 @@
tools:progress="40" tools:progress="40"
tools:visibility="visible" /> tools:visibility="visible" />
<!-- Player will be inserted here in realtime -->
<FrameLayout
android:id="@+id/player_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout> </FrameLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
@ -149,7 +156,6 @@
android:id="@+id/detail_content_root_layout" android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll"> app:layout_scrollFlags="scroll">
<!-- TITLE --> <!-- TITLE -->
@ -543,25 +549,122 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager" android:id="@+id/viewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom|center"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp"/>
<com.google.android.material.tabs.TabLayout </androidx.viewpager.widget.ViewPager>
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
</com.google.android.material.tabs.TabLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<RelativeLayout
android:id="@+id/overlay_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.9"
android:background="?attr/windowBackground" >
<ImageButton
android:paddingLeft="@dimen/video_item_search_padding"
android:id="@+id/overlay_thumbnail"
android:layout_width="50dp"
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:scaleType="fitCenter"
android:gravity="center_vertical"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/dummy_thumbnail"
android:background="@color/transparent_background_color"/>
<LinearLayout
android:id="@+id/overlay_metadata_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"
android:clickable="true"
android:focusable="true"
android:layout_toEndOf="@+id/overlay_thumbnail"
android:layout_toStartOf="@+id/overlay_buttons_layout"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/overlay_title_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONVideo Title LONG very LONG"/>
<TextView
android:id="@+id/overlay_channel_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="The Video Artist LONG very LONG very Long"/>
</LinearLayout>
<LinearLayout
android:id="@+id/overlay_buttons_layout"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:gravity="center_vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"
android:layout_alignParentEnd="true"
tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/overlay_play_pause_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:padding="10dp"
android:scaleType="center"
app:srcCompat="?attr/ic_play_arrow"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<ImageButton
android:id="@+id/overlay_close_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginLeft="2dp"
android:padding="10dp"
android:scaleType="center"
app:srcCompat="?attr/ic_close"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout>
</RelativeLayout>
</FrameLayout> </FrameLayout>

View file

@ -2,32 +2,24 @@
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@color/black"
android:gravity="center"> android:gravity="center">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout <org.schabi.newpipe.views.ExpandableSurfaceView
android:id="@+id/aspectRatioLayout" android:id="@+id/surfaceView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true" android:layout_centerInParent="true"/>
android:layout_gravity="center">
<SurfaceView <View
android:id="@+id/surfaceView" android:id="@+id/surfaceForeground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"/> android:background="@android:color/black"
android:layout_alignBottom="@+id/surfaceView"/>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<com.google.android.exoplayer2.ui.SubtitleView <com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView" android:id="@+id/subtitleView"
@ -46,83 +38,6 @@
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:visibility="visible"/> tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="40dp"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<ImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playbackControlRoot" android:id="@+id/playbackControlRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -131,37 +46,70 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<View
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/player_controls_top_background"
android:layout_alignParentTop="true" />
<View
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/player_controls_background"
android:layout_alignParentBottom="true" />
<!-- All top controls in this layout -->
<RelativeLayout <RelativeLayout
android:id="@+id/playbackWindowRoot" android:id="@+id/playbackWindowRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<RelativeLayout <LinearLayout
android:id="@+id/topControls" android:id="@+id/topControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:background="@drawable/player_controls_top_background" android:orientation="vertical"
android:gravity="top"
android:paddingTop="@dimen/player_main_top_padding"
android:paddingStart="@dimen/player_main_controls_padding"
android:paddingEnd="@dimen/player_main_controls_padding"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/primaryControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="45dp"
android:baselineAligned="false"
android:gravity="top" android:gravity="top"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/playerCloseButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:src="?attr/ic_close"
android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone" />
<LinearLayout <LinearLayout
android:id="@+id/metadataView" android:id="@+id/metadataView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/qualityTextView"
android:gravity="top" android:gravity="top"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="8dp" android:layout_marginTop="6dp"
android:paddingRight="8dp" android:layout_marginRight="8dp"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded"
android:layout_weight="1">
<TextView <TextView
android:id="@+id/titleTextView" android:id="@+id/titleTextView"
@ -200,70 +148,67 @@
android:id="@+id/qualityTextView" android:id="@+id/qualityTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="2dp" android:minWidth="0dp"
android:layout_marginRight="2dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_toLeftOf="@+id/playbackSpeed" android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:minWidth="50dp"
android:text="720p" android:text="720p"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:visibility="visible"
tools:ignore="HardcodedText,RtlHardcoded"/> tools:ignore="HardcodedText,RtlHardcoded"/>
<TextView <TextView
android:id="@+id/playbackSpeed" android:id="@+id/playbackSpeed"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="35dp"
android:layout_marginRight="2dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_toLeftOf="@+id/queueButton" android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:minHeight="35dp" android:minWidth="0dp"
android:minWidth="40dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="1x" /> tools:text="1x"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/queueButton" android:id="@+id/queueButton"
android:layout_width="30dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="2dp" android:paddingTop="5dp"
android:layout_marginRight="2dp" android:paddingStart="3dp"
android:layout_toLeftOf="@+id/moreOptionsButton" android:paddingEnd="3dp"
android:paddingBottom="3dp"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp" android:scaleType="fitCenter"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_list_white_24dp" app:srcCompat="@drawable/ic_list_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/> tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moreOptionsButton" android:id="@+id/moreOptionsButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginLeft="2dp"
android:padding="5dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_expand_more_white_24dp" app:srcCompat="@drawable/ic_expand_more_white_24dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription,RtlHardcoded"/> tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<RelativeLayout </LinearLayout>
<LinearLayout
android:id="@+id/secondaryControls" android:id="@+id/secondaryControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/topControls"
android:gravity="top" android:gravity="top"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:visibility="invisible" android:visibility="invisible"
tools:ignore="RtlHardcoded" tools:ignore="RtlHardcoded"
tools:visibility="visible"> tools:visibility="visible">
@ -272,9 +217,8 @@
android:id="@+id/resizeTextView" android:id="@+id/resizeTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginLeft="8dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:layout_alignParentLeft="true"
android:gravity="center" android:gravity="center"
android:minWidth="50dp" android:minWidth="50dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
@ -287,144 +231,117 @@
android:id="@+id/captionTextView" android:id="@+id/captionTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:layout_toLeftOf="@id/switchMute"
android:layout_toRightOf="@id/resizeTextView"
android:gravity="center|left" android:gravity="center|left"
android:minHeight="35dp" android:minHeight="35dp"
android:minWidth="40dp" android:maxWidth="100dp"
android:paddingLeft="2dp" android:lines="1"
android:paddingRight="2dp" android:ellipsize="end"
android:minWidth="50dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textStyle="bold" android:textStyle="bold"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="RelativeOverlap,RtlHardcoded" tools:ignore="RelativeOverlap,RtlHardcoded"
tools:text="English" /> tools:text="English"/>
<ImageButton <Space
android:id="@+id/kodi" android:id="@+id/spaceBeforeButton"
android:layout_width="30dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="0dp"
android:layout_marginLeft="4dp" android:layout_weight="3"/>
android:layout_marginRight="2dp"
android:layout_alignParentRight="true" <androidx.appcompat.widget.AppCompatImageButton
android:layout_centerVertical="true" android:id="@+id/playWithKodi"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_cast_white_24dp" app:srcCompat="@drawable/ic_cast_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/play_with_kodi_title" android:contentDescription="@string/play_with_kodi_title"
android:visibility="gone" tools:ignore="RtlHardcoded"/>
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/share" android:id="@+id/openInBrowser"
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="35dp"
android:layout_marginLeft="4dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="2dp" android:layout_marginEnd="8dp"
android:layout_toLeftOf="@id/kodi" android:clickable="true"
android:layout_alignWithParentIfMissing="true" android:focusable="true"
android:layout_centerVertical="true" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_language_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/open_in_browser"
tools:ignore="RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_marginEnd="8dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_share_white_24dp" app:srcCompat="@drawable/ic_share_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/share" android:contentDescription="@string/share"
tools:ignore="RtlHardcoded"/> tools:ignore="RtlHardcoded"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/toggleOrientation"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/share"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_screen_rotation_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/toggle_orientation"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchPopup"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/toggleOrientation"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_fullscreen_exit_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_popup"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchBackground"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/switchPopup"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_headset_white_24dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_background"
tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/switchMute" android:id="@+id/switchMute"
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="37dp"
android:layout_marginLeft="4dp" android:padding="@dimen/player_main_buttons_padding"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/switchBackground"
android:layout_centerVertical="true"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="5dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:srcCompat="@drawable/ic_volume_off_white_24dp" app:srcCompat="@drawable/ic_volume_off_white_24dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/switch_to_background" android:contentDescription="@string/mute"
tools:ignore="RtlHardcoded" /> tools:ignore="RtlHardcoded" />
</RelativeLayout> </LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/fullScreenButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="@dimen/player_main_buttons_padding"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout <LinearLayout
android:id="@+id/bottomControls" android:id="@+id/bottomControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="40dp"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:background="@drawable/player_controls_background"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingLeft="16dp" android:paddingLeft="@dimen/player_main_controls_padding"
android:paddingRight="16dp"> android:paddingRight="@dimen/player_main_controls_padding">
<TextView <TextView
android:id="@+id/playbackCurrentTime" android:id="@+id/playbackCurrentTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:minHeight="40dp" android:minHeight="30dp"
android:text="-:--:--" android:text="-:--:--"
android:textColor="@android:color/white" android:textColor="@android:color/white"
tools:ignore="HardcodedText" tools:ignore="HardcodedText"
@ -465,67 +382,145 @@
android:visibility="gone" android:visibility="gone"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" /> tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/screenRotationButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:padding="@dimen/player_main_buttons_padding"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
<ImageButton <LinearLayout
android:id="@+id/playPauseButton" android:layout_width="match_parent"
android:layout_width="100dp" android:layout_height="match_parent"
android:layout_height="100dp" android:gravity="center"
android:layout_centerInParent="true" android:orientation="horizontal"
android:weightSum="5.5">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPreviousButton"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:layout_marginEnd="10dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_previous_white_24dp"
tools:ignore="ContentDescription"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPauseButton"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_pause_white_24dp" app:srcCompat="@drawable/ic_pause_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playPreviousButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:layout_centerInParent="true"
android:layout_toStartOf="@id/playPauseButton"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/playNextButton" android:id="@+id/playNextButton"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="50dp" android:layout_height="40dp"
android:layout_marginStart="30dp" android:layout_weight="1"
android:layout_centerInParent="true" android:layout_marginStart="10dp"
android:layout_toEndOf="@id/playPauseButton"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitCenter"
android:src="@drawable/exo_controls_next" app:srcCompat="@drawable/ic_next_white_24dp"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<Button </LinearLayout>
android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/playPauseButton"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="invisible" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -565,10 +560,7 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_toEndOf="@+id/loading_panel"
android:layout_toRightOf="@+id/loading_panel"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<RelativeLayout <RelativeLayout
@ -643,4 +635,42 @@
tools:visibility="visible" /> tools:visibility="visible" />
</RelativeLayout> </RelativeLayout>
<TextView
android:id="@+id/resizing_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:background="#6e000000"
android:gravity="center"
android:padding="5dp"
android:text="@string/popup_resizing_indicator_title"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="gone" />
<View
android:id="@+id/closingOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AAFF0000"
android:visibility="gone" />
<Button
android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/close"
android:textAllCaps="true"
android:textColor="@color/white"
android:visibility="gone" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,299 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:gravity="center"
tools:layout_height="84dp"
tools:layout_width="@dimen/popup_minimum_width">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id="@+id/aspectRatioLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View
android:id="@+id/surfaceForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<ImageView
android:id="@+id/endScreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:visibility="gone"
tools:background="@android:color/white"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/playbackControlRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#32000000"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/videoPlayPause"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_centerHorizontal="false"
android:layout_centerInParent="true"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<RelativeLayout
android:id="@+id/topControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="@drawable/player_controls_top_background"
android:paddingBottom="20dp"
android:paddingLeft="2dp"
android:paddingRight="6dp"
android:paddingTop="4dp"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/qualityTextView"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentLeft="true"
android:gravity="center"
android:padding="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="1080p60"/>
<TextView
android:id="@+id/playbackSpeed"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_toRightOf="@+id/qualityTextView"
android:gravity="center"
android:padding="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="RelativeOverlap,RtlHardcoded,RtlSymmetry"
tools:text="1.75x"/>
<RelativeLayout
android:id="@+id/extraOptionsView"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_toRightOf="@+id/playbackSpeed"
android:layout_toLeftOf="@id/fullScreenButton"
android:visibility="gone">
<TextView
android:id="@+id/resizeTextView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:minWidth="50dp"
android:padding="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:ignore="HardcodedText,RtlHardcoded"
tools:text="FIT" />
<TextView
android:id="@+id/captionTextView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="5dp"
android:layout_toRightOf="@id/resizeTextView"
android:gravity="center|left"
android:minWidth="40dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="RelativeOverlap,RtlHardcoded,RtlSymmetry"
tools:text="English" />
</RelativeLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/fullScreenButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded" />
</RelativeLayout>
<!--Shadow Bottom Control-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/player_controls_background"
android:orientation="horizontal"
android:paddingTop="50dp"/>
<LinearLayout
android:id="@+id/bottomControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="2dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<TextView
android:id="@+id/playbackCurrentTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="-:--:--"
android:textColor="@android:color/white"
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry"
tools:text="1:06:29"/>
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/playbackSeekBar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
tools:progress="25"
tools:secondaryProgress="50"/>
<TextView
android:id="@+id/playbackEndTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="-:--:--"
android:textColor="@android:color/white"
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry"
tools:text="1:23:49"/>
<TextView
android:id="@+id/playbackLiveSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:gravity="center_vertical"
android:text="@string/duration_live"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:visibility="gone"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="5.5">
<!--tools:visibility="gone">-->
<ImageView
android:id="@+id/controlAnimationView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
app:srcCompat="@drawable/ic_fast_rewind_white_24dp"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
</LinearLayout>
<TextView
android:id="@+id/currentDisplaySeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="5dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="5dp"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="1:06:29"
tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/loading_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/black"
android:gravity="center"
android:padding="20dp"
tools:visibility="gone">
<ProgressBar
android:id="@+id/progressBarLoadingPanel"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</RelativeLayout>
<TextView
android:id="@+id/resizing_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:background="#6e000000"
android:gravity="center"
android:padding="5dp"
android:text="@string/popup_resizing_indicator_title"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="gone"/>
<View
android:id="@+id/closingOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AAFF0000"
android:visibility="gone"/>
</FrameLayout>

View file

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/notificationContent"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="@color/background_notification_color"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/notificationCover"
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="ContentDescription"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:orientation="vertical"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/notificationSongName"
android:layout_width="match_parent"
android:ellipsize="end"
android:layout_height="wrap_content"
android:maxLines="1"
android:textSize="14sp"
android:textColor="@color/background_title_color"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."/>
<TextView
android:id="@+id/notificationArtist"
android:layout_width="match_parent"
android:ellipsize="end"
android:layout_height="wrap_content"
android:maxLines="1"
android:textSize="12sp"
android:textColor="@color/background_subtext_color"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
<ImageButton
android:id="@+id/notificationRepeat"
android:layout_width="40dp"
android:layout_height="match_parent"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/exo_controls_repeat_all"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationPlayPause"
android:layout_width="45dp"
android:layout_height="match_parent"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
android:src="@drawable/exo_controls_pause"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationStop"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="5dp"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_close_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout>

View file

@ -19,15 +19,6 @@
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar"
app:titleTextAppearance="@style/Toolbar.Title"> app:titleTextAppearance="@style/Toolbar.Title">
<Spinner
android:id="@+id/toolbar_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<include <include
android:id="@+id/toolbar_search_container" android:id="@+id/toolbar_search_container"
layout="@layout/toolbar_search_layout" layout="@layout/toolbar_search_layout"

View file

@ -7,4 +7,9 @@
android:orderInCategory="1999" android:orderInCategory="1999"
android:title="@string/switch_to_popup" android:title="@string/switch_to_popup"
app:showAsAction="never"/> app:showAsAction="never"/>
<item android:id="@+id/action_switch_background"
android:orderInCategory="999"
android:title="@string/switch_to_background"
app:showAsAction="never"/>
</menu> </menu>

View file

@ -1,10 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.schabi.newpipe.history.HistoryActivity">
<item android:id="@+id/action_switch_background"
android:orderInCategory="1999"
android:title="@string/switch_to_background"
app:showAsAction="never"/>
</menu>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
android:id="@+id/menu_video_options"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/toggleOrientation"
android:icon="@drawable/ic_screen_rotation_white_24dp"
android:title="@string/toggle_orientation"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/switchPopup"
android:icon="@drawable/ic_fullscreen_exit_white_24dp"
android:title="@string/switch_to_popup"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/switchBackground"
android:icon="?attr/ic_headset"
android:title="@string/switch_to_background"
app:showAsAction="ifRoom|withText" />
</menu>

View file

@ -227,8 +227,6 @@
<string name="trending">الشائعة</string> <string name="trending">الشائعة</string>
<string name="top_50">أفضل 50</string> <string name="top_50">أفضل 50</string>
<string name="new_and_hot">جديد وساخن</string> <string name="new_and_hot">جديد وساخن</string>
<string name="title_activity_background_player">مشغل الخلفية</string>
<string name="title_activity_popup_player">المشغل المنبثق</string>
<string name="play_queue_remove">حذف</string> <string name="play_queue_remove">حذف</string>
<string name="play_queue_stream_detail">التفاصيل</string> <string name="play_queue_stream_detail">التفاصيل</string>
<string name="play_queue_audio_settings">الإعدادات الصوتية</string> <string name="play_queue_audio_settings">الإعدادات الصوتية</string>

View file

@ -273,8 +273,6 @@
<string name="player_stream_failure">无法播放此串流</string> <string name="player_stream_failure">无法播放此串流</string>
<string name="player_unrecoverable_failure">发生无法恢复播放器错误</string> <string name="player_unrecoverable_failure">发生无法恢复播放器错误</string>
<string name="player_recoverable_failure">恢复播放器错误</string> <string name="player_recoverable_failure">恢复播放器错误</string>
<string name="title_activity_background_player">后台播放</string>
<string name="title_activity_popup_player">悬浮窗播放器</string>
<string name="play_queue_remove">移除</string> <string name="play_queue_remove">移除</string>
<string name="play_queue_stream_detail">详情</string> <string name="play_queue_stream_detail">详情</string>
<string name="play_queue_audio_settings">音频设置</string> <string name="play_queue_audio_settings">音频设置</string>

View file

@ -314,8 +314,6 @@
<string name="trending">Трэнды</string> <string name="trending">Трэнды</string>
<string name="top_50">Топ 50</string> <string name="top_50">Топ 50</string>
<string name="new_and_hot">Новае і гарачае</string> <string name="new_and_hot">Новае і гарачае</string>
<string name="title_activity_background_player">У фоне</string>
<string name="title_activity_popup_player">У акне</string>
<string name="play_queue_remove">Выдаліць</string> <string name="play_queue_remove">Выдаліць</string>
<string name="play_queue_stream_detail">Падрабязнасці</string> <string name="play_queue_stream_detail">Падрабязнасці</string>
<string name="play_queue_audio_settings">Налады аўдыё</string> <string name="play_queue_audio_settings">Налады аўдыё</string>

View file

@ -307,8 +307,6 @@
<string name="trending">Набиращи популярност</string> <string name="trending">Набиращи популярност</string>
<string name="top_50">Топ 50</string> <string name="top_50">Топ 50</string>
<string name="new_and_hot">Ново и горещо</string> <string name="new_and_hot">Ново и горещо</string>
<string name="title_activity_background_player">Във фонов режим</string>
<string name="title_activity_popup_player">В прозорец</string>
<string name="play_queue_remove">Премахни</string> <string name="play_queue_remove">Премахни</string>
<string name="play_queue_stream_detail">Детайли</string> <string name="play_queue_stream_detail">Детайли</string>
<string name="play_queue_audio_settings">Аудио настройки</string> <string name="play_queue_audio_settings">Аудио настройки</string>

View file

@ -259,8 +259,6 @@
<string name="kiosk">Quiosc</string> <string name="kiosk">Quiosc</string>
<string name="trending">Tendències</string> <string name="trending">Tendències</string>
<string name="top_50">Els millors 50</string> <string name="top_50">Els millors 50</string>
<string name="title_activity_background_player">Reproductor en rerefons</string>
<string name="title_activity_popup_player">Reproductor emergent</string>
<string name="enqueue_on_background">Afegeix a la cua de reproducció en rerefons</string> <string name="enqueue_on_background">Afegeix a la cua de reproducció en rerefons</string>
<string name="enqueue_on_popup">Afegeix a la cua de reproducció emergent</string> <string name="enqueue_on_popup">Afegeix a la cua de reproducció emergent</string>
<string name="start_here_on_main">Reprodueix aquí</string> <string name="start_here_on_main">Reprodueix aquí</string>

View file

@ -188,7 +188,6 @@
<string name="theme_title">ڕووكار</string> <string name="theme_title">ڕووكار</string>
<string name="switch_to_main">گۆڕین بۆ سەرەکی</string> <string name="switch_to_main">گۆڕین بۆ سەرەکی</string>
<string name="show_original_time_ago_summary">دەقە بنچینەییەکان لە خزمەتگوزارییەکانەوە لە بابەتی پەخشەکاندا دیار دەبن</string> <string name="show_original_time_ago_summary">دەقە بنچینەییەکان لە خزمەتگوزارییەکانەوە لە بابەتی پەخشەکاندا دیار دەبن</string>
<string name="title_activity_popup_player">کارپێکەری پەنجەرەی بچووک</string>
<string name="download_dialog_title">داگرتن</string> <string name="download_dialog_title">داگرتن</string>
<string name="caption_setting_title">ژێرنووسەکان</string> <string name="caption_setting_title">ژێرنووسەکان</string>
<string name="invalid_url_toast">بەستەر هەڵەیە</string> <string name="invalid_url_toast">بەستەر هەڵەیە</string>
@ -601,7 +600,6 @@
<string name="title_last_played">دواین کارپێکراو</string> <string name="title_last_played">دواین کارپێکراو</string>
<string name="could_not_setup_download_menu">ناتوانرێ لیستی داگرتن دابنرێ</string> <string name="could_not_setup_download_menu">ناتوانرێ لیستی داگرتن دابنرێ</string>
<string name="import_export_title">هێنانەوە/هاوردەکردن</string> <string name="import_export_title">هێنانەوە/هاوردەکردن</string>
<string name="title_activity_background_player">کارپێکەری پاشبنەما</string>
<string name="app_update_notification_content_title">وەشانی نوێی داوانامە بەردەستە!</string> <string name="app_update_notification_content_title">وەشانی نوێی داوانامە بەردەستە!</string>
<string name="playlist_thumbnail_change_success">وێنۆچکەی خشتەی کارپێکردن گۆڕدرا.</string> <string name="playlist_thumbnail_change_success">وێنۆچکەی خشتەی کارپێکردن گۆڕدرا.</string>
<string name="import_soundcloud_instructions">هێنانەوەی پەڕەی کەسی SoundCloud بەدانانی بەستەر یاخوود ئایدی: <string name="import_soundcloud_instructions">هێنانەوەی پەڕەی کەسی SoundCloud بەدانانی بەستەر یاخوود ئایدی:

View file

@ -234,8 +234,6 @@ otevření ve vyskakovacím okně</string>
<string name="trending">Trendy</string> <string name="trending">Trendy</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Nové &amp; hot</string> <string name="new_and_hot">Nové &amp; hot</string>
<string name="title_activity_background_player">Přehrávač na pozadí</string>
<string name="title_activity_popup_player">Přehrávač v okně</string>
<string name="play_queue_remove">Odebrat</string> <string name="play_queue_remove">Odebrat</string>
<string name="play_queue_stream_detail">Podrobnosti</string> <string name="play_queue_stream_detail">Podrobnosti</string>
<string name="play_queue_audio_settings">Nastavení zvuku</string> <string name="play_queue_audio_settings">Nastavení zvuku</string>

View file

@ -315,8 +315,6 @@
<string name="trending">Populært lige nu</string> <string name="trending">Populært lige nu</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Nyt og populært</string> <string name="new_and_hot">Nyt og populært</string>
<string name="title_activity_background_player">Baggrundsafspiller</string>
<string name="title_activity_popup_player">Pop op-afspiller</string>
<string name="play_queue_remove">Fjern</string> <string name="play_queue_remove">Fjern</string>
<string name="play_queue_stream_detail">Detaljer</string> <string name="play_queue_stream_detail">Detaljer</string>
<string name="play_queue_audio_settings">Lydindstillinger</string> <string name="play_queue_audio_settings">Lydindstillinger</string>

View file

@ -225,8 +225,6 @@
<string name="subscription_page_summary">Abonnement-Seite</string> <string name="subscription_page_summary">Abonnement-Seite</string>
<string name="feed_page_summary">Feed-Seite</string> <string name="feed_page_summary">Feed-Seite</string>
<string name="channel_page_summary">Kanal-Seite</string> <string name="channel_page_summary">Kanal-Seite</string>
<string name="title_activity_background_player">Wiedergabe im Hintergrund</string>
<string name="title_activity_popup_player">Pop-up Player</string>
<string name="play_queue_stream_detail">Details</string> <string name="play_queue_stream_detail">Details</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="player_unrecoverable_failure">Nicht behebbarer Wiedergabefehler aufgetreten</string> <string name="player_unrecoverable_failure">Nicht behebbarer Wiedergabefehler aufgetreten</string>

View file

@ -307,8 +307,6 @@
<string name="override_current_data">Αυτό θα παρακάμψει τις τρέχουσες ρυθμίσεις σας.</string> <string name="override_current_data">Αυτό θα παρακάμψει τις τρέχουσες ρυθμίσεις σας.</string>
<string name="import_settings">Θέλετε επίσης να εισάγετε ρυθμίσεις;</string> <string name="import_settings">Θέλετε επίσης να εισάγετε ρυθμίσεις;</string>
<string name="kiosk">Περίπτερο</string> <string name="kiosk">Περίπτερο</string>
<string name="title_activity_background_player">Συσκευή αναπαραγωγής Παρασκηνίου</string>
<string name="title_activity_popup_player">Συσκευή αναπαραγωγής Αναδυόμενου παραθύρου</string>
<string name="play_queue_remove">Αφαίρεση</string> <string name="play_queue_remove">Αφαίρεση</string>
<string name="play_queue_stream_detail">Λεπτομέρειες</string> <string name="play_queue_stream_detail">Λεπτομέρειες</string>
<string name="play_queue_audio_settings">Ρυθμίσεις ήχου</string> <string name="play_queue_audio_settings">Ρυθμίσεις ήχου</string>

View file

@ -162,8 +162,6 @@
<string name="player_stream_failure">Ne povis ludi tion torenton</string> <string name="player_stream_failure">Ne povis ludi tion torenton</string>
<string name="player_unrecoverable_failure">Neatendebla eraro de ludilo okazis</string> <string name="player_unrecoverable_failure">Neatendebla eraro de ludilo okazis</string>
<string name="player_recoverable_failure">Reakiri el eraro de la ludilo</string> <string name="player_recoverable_failure">Reakiri el eraro de la ludilo</string>
<string name="title_activity_background_player">Fona ludilo</string>
<string name="title_activity_popup_player">Ŝprucfenestra ludilo</string>
<string name="play_queue_remove">Retiri</string> <string name="play_queue_remove">Retiri</string>
<string name="play_queue_stream_detail">Detalado</string> <string name="play_queue_stream_detail">Detalado</string>
<string name="play_queue_audio_settings">Sonaj parametroj</string> <string name="play_queue_audio_settings">Sonaj parametroj</string>

View file

@ -230,8 +230,6 @@
<string name="player_stream_failure">No se pudo reproducir este stream</string> <string name="player_stream_failure">No se pudo reproducir este stream</string>
<string name="player_unrecoverable_failure">Se produjo un error irrecuperable del reproductor</string> <string name="player_unrecoverable_failure">Se produjo un error irrecuperable del reproductor</string>
<string name="player_recoverable_failure">Recuperándose del error del reproductor</string> <string name="player_recoverable_failure">Recuperándose del error del reproductor</string>
<string name="title_activity_background_player">Reproductor en segundo plano</string>
<string name="title_activity_popup_player">Reproductor emergente</string>
<string name="play_queue_remove">Quitar</string> <string name="play_queue_remove">Quitar</string>
<string name="play_queue_stream_detail">Detalles</string> <string name="play_queue_stream_detail">Detalles</string>
<string name="play_queue_audio_settings">Configuración de audio</string> <string name="play_queue_audio_settings">Configuración de audio</string>

View file

@ -295,8 +295,6 @@
<string name="trending">Trendid</string> <string name="trending">Trendid</string>
<string name="top_50">"Top 50 "</string> <string name="top_50">"Top 50 "</string>
<string name="new_and_hot">Uus ja kuum</string> <string name="new_and_hot">Uus ja kuum</string>
<string name="title_activity_background_player">Taustapleier</string>
<string name="title_activity_popup_player">Hüpikpleier</string>
<string name="play_queue_remove">Eemalda</string> <string name="play_queue_remove">Eemalda</string>
<string name="play_queue_stream_detail">Üksikasjad</string> <string name="play_queue_stream_detail">Üksikasjad</string>
<string name="play_queue_audio_settings">Heli seaded</string> <string name="play_queue_audio_settings">Heli seaded</string>

View file

@ -243,8 +243,6 @@
<string name="trending">Joerak</string> <string name="trending">Joerak</string>
<string name="top_50">Lehen 50ak</string> <string name="top_50">Lehen 50ak</string>
<string name="new_and_hot">Berria eta arrakastatsua</string> <string name="new_and_hot">Berria eta arrakastatsua</string>
<string name="title_activity_background_player">Bigarren planoko erreproduzigailua</string>
<string name="title_activity_popup_player">Laster-leiho erreproduzigailua</string>
<string name="play_queue_remove">Kendu</string> <string name="play_queue_remove">Kendu</string>
<string name="play_queue_stream_detail">Xehetasunak</string> <string name="play_queue_stream_detail">Xehetasunak</string>
<string name="play_queue_audio_settings">Audio ezarpenak</string> <string name="play_queue_audio_settings">Audio ezarpenak</string>

View file

@ -258,7 +258,6 @@
<string name="trending">محبوب</string> <string name="trending">محبوب</string>
<string name="top_50">۵۰ ویدئوی برتر</string> <string name="top_50">۵۰ ویدئوی برتر</string>
<string name="new_and_hot">جدید و داغ</string> <string name="new_and_hot">جدید و داغ</string>
<string name="title_activity_background_player">پخش‌کننده پس‌زمینه</string>
<string name="play_queue_remove">حذف</string> <string name="play_queue_remove">حذف</string>
<string name="play_queue_stream_detail">جزئیات</string> <string name="play_queue_stream_detail">جزئیات</string>
<string name="play_queue_audio_settings">تنظیمات صدا</string> <string name="play_queue_audio_settings">تنظیمات صدا</string>
@ -371,7 +370,6 @@
<string name="popup_playing_append">قرار دادن در صف پخش به صورت تصویر در تصویر</string> <string name="popup_playing_append">قرار دادن در صف پخش به صورت تصویر در تصویر</string>
<string name="player_unrecoverable_failure">خطای عدم احیای پخش‌کننده رخ داد</string> <string name="player_unrecoverable_failure">خطای عدم احیای پخش‌کننده رخ داد</string>
<string name="player_recoverable_failure">در حال احیا از خطای پخش‌کننده</string> <string name="player_recoverable_failure">در حال احیا از خطای پخش‌کننده</string>
<string name="title_activity_popup_player">پخش‌کننده تصویر در تصویر</string>
<string name="enqueue_on_background">در صف پخش پس‌زمینه قرار بده</string> <string name="enqueue_on_background">در صف پخش پس‌زمینه قرار بده</string>
<string name="enqueue_on_popup">در صف پخش تصویر در تصویر قرار بده</string> <string name="enqueue_on_popup">در صف پخش تصویر در تصویر قرار بده</string>
<string name="start_here_on_main">شروع پخش در اینجا</string> <string name="start_here_on_main">شروع پخش در اینجا</string>

View file

@ -230,8 +230,6 @@
<string name="player_stream_failure">Tätä suoratoistosisältöä ei voitu toistaa</string> <string name="player_stream_failure">Tätä suoratoistosisältöä ei voitu toistaa</string>
<string name="player_unrecoverable_failure">Palautuskelvoton soittimen virhe</string> <string name="player_unrecoverable_failure">Palautuskelvoton soittimen virhe</string>
<string name="player_recoverable_failure">Palaudutaan soittimen virheestä</string> <string name="player_recoverable_failure">Palaudutaan soittimen virheestä</string>
<string name="title_activity_background_player">Taustasoitin</string>
<string name="title_activity_popup_player">Ponnahdusikkunasoitin</string>
<string name="play_queue_remove">Poista</string> <string name="play_queue_remove">Poista</string>
<string name="play_queue_stream_detail">Yksityiskohdat</string> <string name="play_queue_stream_detail">Yksityiskohdat</string>
<string name="play_queue_audio_settings">Ääniasetukset</string> <string name="play_queue_audio_settings">Ääniasetukset</string>

View file

@ -224,8 +224,6 @@
<string name="player_stream_failure">Impossible de lire ce flux</string> <string name="player_stream_failure">Impossible de lire ce flux</string>
<string name="player_unrecoverable_failure">Une erreur irrécupérable du lecteur est survenue</string> <string name="player_unrecoverable_failure">Une erreur irrécupérable du lecteur est survenue</string>
<string name="no_channel_subscribed_yet">Pas encore dabonnements de chaîne</string> <string name="no_channel_subscribed_yet">Pas encore dabonnements de chaîne</string>
<string name="title_activity_background_player">Lecteur en arrière-plan</string>
<string name="title_activity_popup_player">Lecteur flottant</string>
<string name="play_queue_remove">Retirer</string> <string name="play_queue_remove">Retirer</string>
<string name="play_queue_stream_detail">Détails</string> <string name="play_queue_stream_detail">Détails</string>
<string name="play_queue_audio_settings">Paramètres audios</string> <string name="play_queue_audio_settings">Paramètres audios</string>

View file

@ -341,8 +341,6 @@
<string name="trending">Tendencias</string> <string name="trending">Tendencias</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Novo e popular</string> <string name="new_and_hot">Novo e popular</string>
<string name="title_activity_background_player">Reprodutor en segundo plano</string>
<string name="title_activity_popup_player">Reprodutor «popup»</string>
<string name="play_queue_remove">Eliminar</string> <string name="play_queue_remove">Eliminar</string>
<string name="play_queue_stream_detail">Detalles</string> <string name="play_queue_stream_detail">Detalles</string>
<string name="play_queue_audio_settings">Opcións de audio</string> <string name="play_queue_audio_settings">Opcións de audio</string>

View file

@ -243,8 +243,6 @@
<string name="trending">החמים</string> <string name="trending">החמים</string>
<string name="top_50">50 המובילים</string> <string name="top_50">50 המובילים</string>
<string name="new_and_hot">חדש וחם</string> <string name="new_and_hot">חדש וחם</string>
<string name="title_activity_background_player">נגן רקע</string>
<string name="title_activity_popup_player">נגן צף</string>
<string name="play_queue_remove">הסרה</string> <string name="play_queue_remove">הסרה</string>
<string name="play_queue_stream_detail">פרטים</string> <string name="play_queue_stream_detail">פרטים</string>
<string name="play_queue_audio_settings">אפשרויות שמע</string> <string name="play_queue_audio_settings">אפשרויות שמע</string>

View file

@ -232,8 +232,6 @@
<string name="kiosk">kiosk</string> <string name="kiosk">kiosk</string>
<string name="top_50">टॉप 50</string> <string name="top_50">टॉप 50</string>
<string name="new_and_hot">नया और गरम</string> <string name="new_and_hot">नया और गरम</string>
<string name="title_activity_background_player">पृष्ठभूमि प्लेयर</string>
<string name="title_activity_popup_player">पॉपअप प्लेयर</string>
<string name="play_queue_remove">निकाले</string> <string name="play_queue_remove">निकाले</string>
<string name="play_queue_stream_detail">विवरण</string> <string name="play_queue_stream_detail">विवरण</string>
<string name="hold_to_append">जोड़ने के लिए पकड़ें रहे</string> <string name="hold_to_append">जोड़ने के लिए पकड़ें रहे</string>

View file

@ -234,8 +234,6 @@
<string name="trending">U trendu</string> <string name="trending">U trendu</string>
<string name="top_50">Vrh 50</string> <string name="top_50">Vrh 50</string>
<string name="new_and_hot">Novo i popularno</string> <string name="new_and_hot">Novo i popularno</string>
<string name="title_activity_background_player">Pozadinski player</string>
<string name="title_activity_popup_player">Skočni reproduktor</string>
<string name="play_queue_remove">Ukloni</string> <string name="play_queue_remove">Ukloni</string>
<string name="play_queue_stream_detail">Detalji</string> <string name="play_queue_stream_detail">Detalji</string>
<string name="play_queue_audio_settings">Postavke zvuka</string> <string name="play_queue_audio_settings">Postavke zvuka</string>

View file

@ -296,7 +296,6 @@
<string name="trending">Felkapott</string> <string name="trending">Felkapott</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Új és friss</string> <string name="new_and_hot">Új és friss</string>
<string name="title_activity_popup_player">Felugró ablak lejátszó</string>
<string name="play_queue_remove">Eltávolítás</string> <string name="play_queue_remove">Eltávolítás</string>
<string name="play_queue_stream_detail">Részletek</string> <string name="play_queue_stream_detail">Részletek</string>
<string name="play_queue_audio_settings">Hang beállítások</string> <string name="play_queue_audio_settings">Hang beállítások</string>

View file

@ -270,8 +270,6 @@
<string name="trending">Trending</string> <string name="trending">Trending</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Baru &amp; panas</string> <string name="new_and_hot">Baru &amp; panas</string>
<string name="title_activity_background_player">Pemutar latar belakang</string>
<string name="title_activity_popup_player">Pemutar popup</string>
<string name="play_queue_remove">Hapus</string> <string name="play_queue_remove">Hapus</string>
<string name="play_queue_stream_detail">Detail</string> <string name="play_queue_stream_detail">Detail</string>
<string name="play_queue_audio_settings">Pengaturan Audio</string> <string name="play_queue_audio_settings">Pengaturan Audio</string>

View file

@ -231,8 +231,6 @@
<string name="player_stream_failure">Impossibile riprodurre questo flusso</string> <string name="player_stream_failure">Impossibile riprodurre questo flusso</string>
<string name="player_unrecoverable_failure">Si è verificato un errore irreversibile</string> <string name="player_unrecoverable_failure">Si è verificato un errore irreversibile</string>
<string name="player_recoverable_failure">Ripristino dell\'errore del lettore multimediale</string> <string name="player_recoverable_failure">Ripristino dell\'errore del lettore multimediale</string>
<string name="title_activity_background_player">Riproduzione in Sottofondo</string>
<string name="title_activity_popup_player">Lettore Popup</string>
<string name="play_queue_remove">Rimuovi</string> <string name="play_queue_remove">Rimuovi</string>
<string name="play_queue_stream_detail">Dettagli</string> <string name="play_queue_stream_detail">Dettagli</string>
<string name="play_queue_audio_settings">Impostazioni Audio</string> <string name="play_queue_audio_settings">Impostazioni Audio</string>

View file

@ -216,7 +216,6 @@
<string name="kiosk">Kiosk</string> <string name="kiosk">Kiosk</string>
<string name="trending">人気</string> <string name="trending">人気</string>
<string name="top_50">トップ50</string> <string name="top_50">トップ50</string>
<string name="title_activity_popup_player">ポップアップ再生</string>
<string name="play_queue_remove">削除</string> <string name="play_queue_remove">削除</string>
<string name="play_queue_stream_detail">詳細</string> <string name="play_queue_stream_detail">詳細</string>
<string name="play_queue_audio_settings">音声の設定</string> <string name="play_queue_audio_settings">音声の設定</string>
@ -241,7 +240,6 @@
<string name="no_valid_zip_file">有効な ZIP ファイルではありません</string> <string name="no_valid_zip_file">有効な ZIP ファイルではありません</string>
<string name="could_not_import_all_files">警告: すべてのファイルをインポートできませんでした。</string> <string name="could_not_import_all_files">警告: すべてのファイルをインポートできませんでした。</string>
<string name="override_current_data">これにより、現在の設定が上書きされます。</string> <string name="override_current_data">これにより、現在の設定が上書きされます。</string>
<string name="title_activity_background_player">バックグラウンド再生</string>
<string name="start_here_on_main">ここから再生を開始</string> <string name="start_here_on_main">ここから再生を開始</string>
<string name="start_here_on_background">バックグラウンドで連続再生を開始</string> <string name="start_here_on_background">バックグラウンドで連続再生を開始</string>
<string name="drawer_open">ドロワーを開く</string> <string name="drawer_open">ドロワーを開く</string>

View file

@ -230,8 +230,6 @@
<string name="trending">인기 급상승</string> <string name="trending">인기 급상승</string>
<string name="top_50">탑 50</string> <string name="top_50">탑 50</string>
<string name="new_and_hot">신작 &amp; 뜨는 동영상</string> <string name="new_and_hot">신작 &amp; 뜨는 동영상</string>
<string name="title_activity_background_player">백그라운드 플레이어</string>
<string name="title_activity_popup_player">팝업 플레이어</string>
<string name="play_queue_remove">제거</string> <string name="play_queue_remove">제거</string>
<string name="play_queue_stream_detail">상세 정보</string> <string name="play_queue_stream_detail">상세 정보</string>
<string name="play_queue_audio_settings">오디오 설정</string> <string name="play_queue_audio_settings">오디오 설정</string>

View file

@ -252,7 +252,6 @@
<string name="title_last_played">دواین کارپێکراو</string> <string name="title_last_played">دواین کارپێکراو</string>
<string name="title_most_played">زۆرترین کارپێکردن</string> <string name="title_most_played">زۆرترین کارپێکردن</string>
<string name="main_page_content">ناوەڕۆکی پەڕەی سەرەکی</string> <string name="main_page_content">ناوەڕۆکی پەڕەی سەرەکی</string>
<string name="title_activity_popup_player">کارپێکەری پەنجەرەی بچووک</string>
<string name="play_queue_remove">لادان</string> <string name="play_queue_remove">لادان</string>
<string name="play_queue_stream_detail">وردەکارییەکان</string> <string name="play_queue_stream_detail">وردەکارییەکان</string>
<string name="play_queue_audio_settings">ڕێکخستنەکانی دەنگ</string> <string name="play_queue_audio_settings">ڕێکخستنەکانی دەنگ</string>
@ -434,7 +433,6 @@
<string name="trending">پڕبینەرەکان</string> <string name="trending">پڕبینەرەکان</string>
<string name="top_50">باشترین 50</string> <string name="top_50">باشترین 50</string>
<string name="new_and_hot">نوێ &amp; چالاک</string> <string name="new_and_hot">نوێ &amp; چالاک</string>
<string name="title_activity_background_player">کارپێکەری پاشبنەما</string>
<string name="hold_to_append">پەنجەت داگرە بۆ ڕیزنەبوون</string> <string name="hold_to_append">پەنجەت داگرە بۆ ڕیزنەبوون</string>
<string name="enqueue_on_background">ڕیزنەبوون لە پاشبنەما</string> <string name="enqueue_on_background">ڕیزنەبوون لە پاشبنەما</string>
<string name="enqueue_on_popup">ڕیزنەبوون لە پەنجەرەی بچووک</string> <string name="enqueue_on_popup">ڕیزنەبوون لە پەنجەرەی بچووک</string>

View file

@ -267,8 +267,6 @@
<string name="trending">Tendencijos</string> <string name="trending">Tendencijos</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Nauja ir karšta</string> <string name="new_and_hot">Nauja ir karšta</string>
<string name="title_activity_background_player">Foninis grotuvas</string>
<string name="title_activity_popup_player">Langelio rėžimo grotuvas</string>
<string name="play_queue_remove">Pašalinti</string> <string name="play_queue_remove">Pašalinti</string>
<string name="play_queue_stream_detail">Detalės</string> <string name="play_queue_stream_detail">Detalės</string>
<string name="play_queue_audio_settings">Garso nustatymai</string> <string name="play_queue_audio_settings">Garso nustatymai</string>

View file

@ -290,8 +290,6 @@
<string name="trending">Популарни</string> <string name="trending">Популарни</string>
<string name="top_50">Топ 50</string> <string name="top_50">Топ 50</string>
<string name="new_and_hot">Нови и жешки</string> <string name="new_and_hot">Нови и жешки</string>
<string name="title_activity_background_player">Позадински плеер</string>
<string name="title_activity_popup_player">Подпрозорче</string>
<string name="play_queue_remove">Одстрани</string> <string name="play_queue_remove">Одстрани</string>
<string name="play_queue_stream_detail">Детали</string> <string name="play_queue_stream_detail">Детали</string>
<string name="play_queue_audio_settings">Звучни поставки</string> <string name="play_queue_audio_settings">Звучни поставки</string>

View file

@ -79,8 +79,6 @@
<string name="play_queue_audio_settings">ഓഡിയോ ക്രമീകരണങ്ങൾ</string> <string name="play_queue_audio_settings">ഓഡിയോ ക്രമീകരണങ്ങൾ</string>
<string name="play_queue_stream_detail">വിശദാംശങ്ങൾ</string> <string name="play_queue_stream_detail">വിശദാംശങ്ങൾ</string>
<string name="play_queue_remove">നീക്കം ചെയ്യുക</string> <string name="play_queue_remove">നീക്കം ചെയ്യുക</string>
<string name="title_activity_popup_player">പോപ്-അപ് പ്ലെയർ</string>
<string name="title_activity_background_player">ബാക്ക്ഗ്രൗണ്ട് പ്ലേയർ</string>
<string name="conferences">സമ്മേളനങ്ങൾ</string> <string name="conferences">സമ്മേളനങ്ങൾ</string>
<string name="most_liked">ഏറ്റവും ഇഷ്ടപ്പെട്ടത്</string> <string name="most_liked">ഏറ്റവും ഇഷ്ടപ്പെട്ടത്</string>
<string name="recently_added">സമീപകാലത്ത് ചേർത്തത്</string> <string name="recently_added">സമീപകാലത്ത് ചേർത്തത്</string>

View file

@ -328,8 +328,6 @@
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="new_and_hot">Baru &amp; panas</string> <string name="new_and_hot">Baru &amp; panas</string>
<string name="conferences">Persidangan</string> <string name="conferences">Persidangan</string>
<string name="title_activity_background_player">Pemain latar belakang</string>
<string name="title_activity_popup_player">Pemain popup</string>
<string name="play_queue_remove">Hapuskan</string> <string name="play_queue_remove">Hapuskan</string>
<string name="play_queue_stream_detail">Butiran</string> <string name="play_queue_stream_detail">Butiran</string>
<string name="play_queue_audio_settings">Tetapan Audio</string> <string name="play_queue_audio_settings">Tetapan Audio</string>

View file

@ -222,8 +222,6 @@
<string name="kiosk">Kiosk</string> <string name="kiosk">Kiosk</string>
<string name="top_50">Topp 50</string> <string name="top_50">Topp 50</string>
<string name="new_and_hot">Nytt og hett</string> <string name="new_and_hot">Nytt og hett</string>
<string name="title_activity_background_player">Bakgrunnsavspiller</string>
<string name="title_activity_popup_player">Oppsprettsavspiller</string>
<string name="play_queue_remove">Fjern</string> <string name="play_queue_remove">Fjern</string>
<string name="play_queue_stream_detail">Detaljer</string> <string name="play_queue_stream_detail">Detaljer</string>
<string name="play_queue_audio_settings">Lydinnstillinger</string> <string name="play_queue_audio_settings">Lydinnstillinger</string>

Some files were not shown because too many files have changed in this diff Show more