Merge branch 'dev'
2
.github/CONTRIBUTING.md
vendored
|
@ -32,7 +32,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
|
|||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
|
||||
* Try to figure out yourself why builds on our CI fail.
|
||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about sumission, or clearly state that in the description of your PR.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about submission, or clearly state that in the description of your PR.
|
||||
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
||||
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
|
||||
* Check if your submission can be build with the current fdroid build server setup.
|
||||
|
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1 +1 @@
|
|||
- [ ] I carefully reed the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
|
||||
- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
|
||||
|
|
|
@ -5,11 +5,13 @@ android:
|
|||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-26.0.1
|
||||
- build-tools-27.0.1
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-26
|
||||
- android-27
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-27"
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
|
|
21
README.md
|
@ -13,21 +13,22 @@
|
|||
</p>
|
||||
<hr />
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||
<hr />
|
||||
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="screenshots/shot_1.png" width=160>](screenshots/shot_1.png)
|
||||
[<img src="screenshots/shot_2.png" width=160>](screenshots/shot_2.png)
|
||||
[<img src="screenshots/shot_3.png" width=160>](screenshots/shot_3.png)
|
||||
[<img src="screenshots/shot_4.png" width=160>](screenshots/shot_4.png)
|
||||
[<img src="screenshots/shot_5.png" width=160>](screenshots/shot_5.png)
|
||||
[<img src="screenshots/shot_6.png" width=160>](screenshots/shot_6.png)
|
||||
[<img src="screenshots/shot_7.png" width=160>](screenshots/shot_7.png)
|
||||
[<img src="screenshots/shot_8.png" width=160>](screenshots/shot_8.png)
|
||||
[<img src="screenshots/shot_9.png" width=160>](screenshots/shot_9.png)
|
||||
[<img src="screenshots/shot_10.png" width=160>](screenshots/shot_10.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
|
||||
|
||||
## Description
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion '26.0.1'
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.1'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 26
|
||||
versionCode 41
|
||||
versionName "0.11.0"
|
||||
targetSdkVersion 27
|
||||
versionCode 43
|
||||
versionName "0.11.2"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
@ -27,6 +27,10 @@ android {
|
|||
applicationIdSuffix ".debug"
|
||||
}
|
||||
beta {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
|
||||
applicationIdSuffix ".beta"
|
||||
}
|
||||
}
|
||||
|
@ -43,43 +47,46 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
supportLibVersion = '27.0.2'
|
||||
}
|
||||
dependencies {
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
compile 'com.github.TeamNewPipe:NewPipeExtractor:b9d0941'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2d191c4ca'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||
|
||||
compile 'com.android.support:appcompat-v7:26.0.1'
|
||||
compile 'com.android.support:support-v4:26.0.1'
|
||||
compile 'com.android.support:design:26.0.1'
|
||||
compile 'com.android.support:recyclerview-v7:26.0.1'
|
||||
compile 'com.android.support:preference-v14:26.0.1'
|
||||
implementation "com.android.support:appcompat-v7:$supportLibVersion"
|
||||
implementation "com.android.support:support-v4:$supportLibVersion"
|
||||
implementation "com.android.support:design:$supportLibVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:preference-v14:$supportLibVersion"
|
||||
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.2'
|
||||
implementation 'ch.acra:acra:4.9.2'
|
||||
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.1'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.5.4'
|
||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
|
||||
implementation 'com.nononsenseapps:filepicker:3.0.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
|
||||
|
||||
debugCompile 'com.facebook.stetho:stetho:1.5.0'
|
||||
debugCompile 'com.facebook.stetho:stetho-urlconnection:1.5.0'
|
||||
debugCompile 'com.android.support:multidex:1.0.1'
|
||||
debugImplementation 'com.facebook.stetho:stetho:1.5.0'
|
||||
debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0'
|
||||
debugImplementation 'com.android.support:multidex:1.0.2'
|
||||
|
||||
compile 'io.reactivex.rxjava2:rxjava:2.1.2'
|
||||
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
||||
|
||||
compile 'android.arch.persistence.room:runtime:1.0.0-alpha8'
|
||||
compile 'android.arch.persistence.room:rxjava2:1.0.0-alpha8'
|
||||
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-alpha8'
|
||||
implementation 'android.arch.persistence.room:runtime:1.0.0'
|
||||
implementation 'android.arch.persistence.room:rxjava2:1.0.0'
|
||||
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
|
||||
|
||||
compile 'frankiesardo:icepick:3.2.0'
|
||||
provided 'frankiesardo:icepick-processor:3.2.0'
|
||||
implementation 'frankiesardo:icepick:3.2.0'
|
||||
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@
|
|||
android:name=".RouterPopupActivity"
|
||||
android:label="@string/popup_mode_share_menu_title"
|
||||
android:taskAffinity=""
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
android:theme="@style/PopupPermissionsTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
|
|
|
@ -21,6 +21,7 @@ public class RouterPopupActivity extends RouterActivity {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
|
||||
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
StreamingService service;
|
||||
|
|
|
@ -135,8 +135,12 @@ public class AboutActivity extends AppCompatActivity {
|
|||
View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(new OnGithubLinkClickListener());
|
||||
|
||||
View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
donationLink.setOnClickListener(new OnDonationLinkClickListener());
|
||||
|
||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
websiteLink.setOnClickListener(new OnWebsiteLinkClickListener());
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
@ -149,10 +153,21 @@ public class AboutActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
||||
private static class OnDonationLinkClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
||||
public void onClick(final View view) {
|
||||
final Context context = view.getContext();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.donation_url)));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnWebsiteLinkClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
final Context context = view.getContext();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.website_url)));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,9 @@ public class LicenseFragment extends Fragment {
|
|||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||
|
||||
View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
||||
|
||||
for (final SoftwareComponent component : softwareComponents) {
|
||||
View componentView = inflater.inflate(R.layout.item_software_component, container, false);
|
||||
TextView softwareName = componentView.findViewById(R.id.name);
|
||||
|
@ -119,4 +122,11 @@ public class LicenseFragment extends Fragment {
|
|||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class WatchHistoryEntry extends HistoryEntry {
|
|||
}
|
||||
|
||||
public WatchHistoryEntry(StreamInfo streamInfo) {
|
||||
this(new Date(), streamInfo.service_id, streamInfo.name, streamInfo.url,
|
||||
this(new Date(), streamInfo.getServiceId(), streamInfo.getName(), streamInfo.getUrl(),
|
||||
streamInfo.id, streamInfo.thumbnail_url, streamInfo.uploader_name, streamInfo.duration);
|
||||
}
|
||||
|
||||
|
|
|
@ -116,10 +116,7 @@ public class SubscriptionEntity {
|
|||
|
||||
@Ignore
|
||||
public ChannelInfoItem toChannelInfoItem() {
|
||||
ChannelInfoItem item = new ChannelInfoItem();
|
||||
item.url = getUrl();
|
||||
item.service_id = getServiceId();
|
||||
item.name = getName();
|
||||
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||
item.thumbnail_url = getAvatarUrl();
|
||||
item.subscriber_count = getSubscriberCount();
|
||||
item.description = getDescription();
|
||||
|
|
|
@ -108,8 +108,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
|||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
nameEditText = view.findViewById(R.id.file_name);
|
||||
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.name));
|
||||
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.audio_streams);
|
||||
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
|
||||
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
|
||||
|
||||
streamsSpinner = view.findViewById(R.id.quality_spinner);
|
||||
streamsSpinner.setOnItemSelectedListener(this);
|
||||
|
@ -183,7 +183,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
|||
String[] items = new String[audioStreams.size()];
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
items[i] = MediaFormat.getNameById(audioStream.format) + " " + audioStream.average_bitrate + "kbps";
|
||||
items[i] = audioStream.getFormat().getName() + " " + audioStream.getAverageBitrate() + "kbps";
|
||||
}
|
||||
|
||||
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
|
||||
|
@ -242,7 +242,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
|||
RadioButton audioButton = view.findViewById(R.id.audio_button);
|
||||
RadioButton videoButton = view.findViewById(R.id.video_button);
|
||||
|
||||
if (currentInfo.audio_streams == null || currentInfo.audio_streams.size() == 0) {
|
||||
if (currentInfo.getAudioStreams() == null || currentInfo.getAudioStreams().size() == 0) {
|
||||
audioButton.setVisibility(View.GONE);
|
||||
videoButton.setChecked(true);
|
||||
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
|
||||
|
@ -256,14 +256,18 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
|||
String url, location;
|
||||
|
||||
String fileName = nameEditText.getText().toString().trim();
|
||||
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.name);
|
||||
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
|
||||
|
||||
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
|
||||
url = isAudio ? currentInfo.audio_streams.get(selectedAudioIndex).url : sortedStreamVideosList.get(selectedVideoIndex).url;
|
||||
location = isAudio ? NewPipeSettings.getAudioDownloadPath(getContext()) : NewPipeSettings.getVideoDownloadPath(getContext());
|
||||
|
||||
if (isAudio) fileName += "." + MediaFormat.getSuffixById(currentInfo.audio_streams.get(selectedAudioIndex).format);
|
||||
else fileName += "." + MediaFormat.getSuffixById(sortedStreamVideosList.get(selectedVideoIndex).format);
|
||||
if (isAudio) {
|
||||
url = currentInfo.getAudioStreams().get(selectedAudioIndex).getUrl();
|
||||
location = NewPipeSettings.getAudioDownloadPath(getContext());
|
||||
fileName += "." + currentInfo.getAudioStreams().get(selectedAudioIndex).getFormat().getSuffix();
|
||||
} else {
|
||||
url = sortedStreamVideosList.get(selectedVideoIndex).getUrl();
|
||||
location = NewPipeSettings.getVideoDownloadPath(getContext());
|
||||
fileName += "." + sortedStreamVideosList.get(selectedVideoIndex).getFormat().getSuffix();
|
||||
}
|
||||
|
||||
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
|
||||
getDialog().dismiss();
|
||||
|
|
|
@ -50,6 +50,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||
protected Button errorButtonRetry;
|
||||
protected TextView errorTextView;
|
||||
|
||||
@State
|
||||
protected boolean useAsFrontPage = false;
|
||||
|
||||
@Override
|
||||
|
@ -218,6 +219,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||
|
||||
if (serviceName == null) serviceName = "none";
|
||||
if (request == null) request = "none";
|
||||
|
||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments;
|
|||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -30,30 +31,28 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
|||
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
|
||||
private ViewPager viewPager;
|
||||
private boolean showBlankTab = false;
|
||||
|
||||
private static final int FALLBACK_SERVICE_ID = 0; // Youtbe
|
||||
public int currentServiceId = -1;
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
private static final int FALLBACK_SERVICE_ID = 0; // Youtube
|
||||
private static final String FALLBACK_CHANNEL_URL =
|
||||
"https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ";
|
||||
private static final String FALLBACK_CHANNEL_NAME = "Music";
|
||||
private static final String FALLBACK_KIOSK_ID = "Trending";
|
||||
|
||||
public int currentServiceId = -1;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Konst
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static final int KIOSK_MENU_OFFSETT = 2000;
|
||||
private static final int KIOSK_MENU_OFFSET = 2000;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's LifeCycle
|
||||
|
@ -66,7 +65,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
currentServiceId = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.getString(getString(R.string.current_service_key), "0"));
|
||||
return inflater.inflate(R.layout.fragment_main, container, false);
|
||||
|
@ -86,27 +85,27 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
if(ThemeHelper.isLightThemeSelected(getActivity())) {
|
||||
int channelIcon;
|
||||
int whatsHotIcon;
|
||||
|
||||
if (ThemeHelper.isLightThemeSelected(getActivity())) {
|
||||
tabLayout.setBackgroundColor(getResources().getColor(R.color.light_youtube_primary_color));
|
||||
channelIcon = R.drawable.ic_channel_black_24dp;
|
||||
whatsHotIcon = R.drawable.ic_whatshot_black_24dp;
|
||||
} else {
|
||||
channelIcon = R.drawable.ic_channel_white_24dp;
|
||||
whatsHotIcon = R.drawable.ic_whatshot_white_24dp;
|
||||
}
|
||||
|
||||
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
|
||||
.equals(getString(R.string.subscription_page_key))) {
|
||||
if(ThemeHelper.isLightThemeSelected(getActivity())) {
|
||||
tabLayout.getTabAt(0).setIcon(R.drawable.ic_channel_black_24dp);
|
||||
} else{
|
||||
tabLayout.getTabAt(0).setIcon(R.drawable.ic_channel_white_24dp);
|
||||
}
|
||||
} else {
|
||||
if(ThemeHelper.isLightThemeSelected(getActivity())) {
|
||||
tabLayout.getTabAt(0).setIcon(R.drawable.ic_whatshot_black_24dp);
|
||||
tabLayout.getTabAt(1).setIcon(R.drawable.ic_channel_black_24dp);
|
||||
} else {
|
||||
tabLayout.getTabAt(0).setIcon(R.drawable.ic_whatshot_white_24dp);
|
||||
tabLayout.getTabAt(1).setIcon(R.drawable.ic_channel_white_24dp);
|
||||
}
|
||||
}
|
||||
tabLayout.getTabAt(0).setIcon(channelIcon);
|
||||
} else {
|
||||
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
|
||||
tabLayout.getTabAt(1).setIcon(channelIcon);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -182,7 +181,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
.equals(getString(R.string.subscription_page_key))) {
|
||||
return new SubscriptionFragment();
|
||||
} else {
|
||||
return getMainPageFramgent();
|
||||
return getMainPageFragment();
|
||||
}
|
||||
case 1:
|
||||
return new SubscriptionFragment();
|
||||
|
@ -213,7 +212,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
// Main page content
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private Fragment getMainPageFramgent() {
|
||||
private Fragment getMainPageFragment() {
|
||||
try {
|
||||
SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
@ -268,7 +267,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
KioskList kl = service.getKioskList();
|
||||
int i = 0;
|
||||
for(final String ks : kl.getAvailableKiosks()) {
|
||||
menu.add(0, KIOSK_MENU_OFFSETT + i, Menu.NONE,
|
||||
menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE,
|
||||
KioskTranslator.getTranslatedKioskName(ks, getContext()))
|
||||
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
|
|
|
@ -60,7 +60,7 @@ public class SpinnerToolbarAdapter extends BaseAdapter {
|
|||
ImageView woSoundIcon = convertView.findViewById(R.id.wo_sound_icon);
|
||||
TextView text = convertView.findViewById(android.R.id.text1);
|
||||
VideoStream item = (VideoStream) getItem(position);
|
||||
text.setText(MediaFormat.getNameById(item.format) + " " + item.resolution);
|
||||
text.setText(item.getFormat().getName() + " " + item.getResolution());
|
||||
|
||||
int visibility = !showIconNoAudio ? View.GONE
|
||||
: item.isVideoOnly ? View.VISIBLE
|
||||
|
|
|
@ -4,7 +4,8 @@ import java.io.Serializable;
|
|||
|
||||
class StackItem implements Serializable {
|
||||
private int serviceId;
|
||||
private String title, url;
|
||||
private String title;
|
||||
private String url;
|
||||
|
||||
StackItem(int serviceId, String url, String title) {
|
||||
this.serviceId = serviceId;
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.support.annotation.DrawableRes;
|
|||
import android.support.annotation.FloatRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.text.TextUtilsCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
@ -36,6 +37,7 @@ import android.widget.FrameLayout;
|
|||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
@ -62,9 +64,10 @@ import org.schabi.newpipe.fragments.BackPressable;
|
|||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.history.HistoryListener;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
|
@ -281,7 +284,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
// Check if the next video label and video is visible,
|
||||
// if it is, include the two elements in the next check
|
||||
int nextCount = currentInfo != null && currentInfo.next_video != null ? 2 : 0;
|
||||
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
|
||||
if (relatedStreamsView != null && relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
|
||||
outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
|
||||
}
|
||||
|
@ -328,10 +331,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
openPopupPlayer(false);
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (currentInfo.uploader_url == null || currentInfo.uploader_url.isEmpty()) {
|
||||
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||
Log.w(TAG, "Can't open channel because we got no channel URL");
|
||||
} else {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(), currentInfo.service_id, currentInfo.uploader_url, currentInfo.uploader_name);
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
}
|
||||
break;
|
||||
case R.id.detail_thumbnail_root_layout:
|
||||
|
@ -378,7 +385,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
if (DEBUG) Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "]");
|
||||
if (!showRelatedStreams) return;
|
||||
|
||||
int nextCount = info.next_video != null ? 2 : 0;
|
||||
int nextCount = info.getNextVideo() != null ? 2 : 0;
|
||||
int initialCount = INITIAL_RELATED_VIDEOS + nextCount;
|
||||
|
||||
if (relatedStreamsView.getChildCount() > initialCount) {
|
||||
|
@ -388,8 +395,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
}
|
||||
|
||||
//Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]");
|
||||
for (int i = INITIAL_RELATED_VIDEOS; i < info.related_streams.size(); i++) {
|
||||
InfoItem item = info.related_streams.get(i);
|
||||
for (int i = INITIAL_RELATED_VIDEOS; i < info.getRelatedStreams().size(); i++) {
|
||||
InfoItem item = info.getRelatedStreams().get(i);
|
||||
//Log.d(TAG, "i = " + i);
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
|
||||
}
|
||||
|
@ -457,7 +464,12 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
infoItemBuilder.setOnStreamSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<StreamInfoItem>() {
|
||||
@Override
|
||||
public void selected(StreamInfoItem selectedItem) {
|
||||
selectAndLoadVideo(selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
selectAndLoadVideo(selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(StreamInfoItem selectedItem) {
|
||||
showStreamDialog(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -476,6 +488,34 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
|
||||
}
|
||||
|
||||
private void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
}
|
||||
|
||||
private View.OnTouchListener getOnControlsTouchListener() {
|
||||
return new View.OnTouchListener() {
|
||||
@Override
|
||||
|
@ -497,35 +537,35 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
private void initThumbnailViews(StreamInfo info) {
|
||||
thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
||||
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, DISPLAY_THUMBNAIL_OPTIONS, new SimpleImageLoadingListener() {
|
||||
if (!TextUtils.isEmpty(info.getThumbnailUrl())) {
|
||||
imageLoader.displayImage(info.getThumbnailUrl(), thumbnailImageView, DISPLAY_THUMBNAIL_OPTIONS, new SimpleImageLoadingListener() {
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
|
||||
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentInfo.getServiceId()), imageUri, R.string.could_not_load_thumbnails));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (info.uploader_avatar_url != null && !info.uploader_avatar_url.isEmpty()) {
|
||||
imageLoader.displayImage(info.uploader_avatar_url, uploaderThumb, DISPLAY_AVATAR_OPTIONS);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
|
||||
imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, DISPLAY_AVATAR_OPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
private void initRelatedVideos(StreamInfo info) {
|
||||
if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews();
|
||||
|
||||
if (info.next_video != null && showRelatedStreams) {
|
||||
if (info.getNextVideo() != null && showRelatedStreams) {
|
||||
nextStreamTitle.setVisibility(View.VISIBLE);
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video));
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
|
||||
relatedStreamsView.addView(getSeparatorView());
|
||||
relatedStreamRootLayout.setVisibility(View.VISIBLE);
|
||||
} else nextStreamTitle.setVisibility(View.GONE);
|
||||
|
||||
if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) {
|
||||
//long first = System.nanoTime(), each;
|
||||
int to = info.related_streams.size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS : info.related_streams.size();
|
||||
int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS : info.getRelatedStreams().size();
|
||||
for (int i = 0; i < to; i++) {
|
||||
InfoItem item = info.related_streams.get(i);
|
||||
InfoItem item = info.getRelatedStreams().get(i);
|
||||
//each = System.nanoTime();
|
||||
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
|
||||
//if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
|
||||
|
@ -537,7 +577,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand)));
|
||||
} else {
|
||||
if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
|
||||
if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE);
|
||||
relatedStreamExpandButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
@ -581,14 +621,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
private void setupActionBarHandler(final StreamInfo info) {
|
||||
if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]");
|
||||
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(activity, info.video_streams, info.video_only_streams, false));
|
||||
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false));
|
||||
actionBarHandler.setupStreamList(sortedStreamVideosList, spinnerToolbar);
|
||||
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
|
||||
@Override
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, info.url);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, info.getUrl());
|
||||
intent.setType("text/plain");
|
||||
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
@ -599,7 +639,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
public void onActionSelected(int selectedStreamId) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(info.url));
|
||||
intent.setData(Uri.parse(info.getUrl()));
|
||||
startActivity(Intent.createChooser(intent, activity.getString(R.string.choose_browser)));
|
||||
}
|
||||
});
|
||||
|
@ -608,7 +648,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
@Override
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
try {
|
||||
NavigationHelper.playWithKore(activity, Uri.parse(info.url.replace("https", "http")));
|
||||
NavigationHelper.playWithKore(activity, Uri.parse(info.getUrl().replace("https", "http")));
|
||||
if(activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onVideoPlayed(info, null);
|
||||
}
|
||||
|
@ -707,7 +747,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) {
|
||||
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
|
||||
|
||||
setInitialData(info.service_id, info.url, info.name);
|
||||
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
pushToStack(serviceId, url, name);
|
||||
showLoading();
|
||||
|
||||
|
@ -763,7 +803,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void openBackgroundPlayer(final boolean append) {
|
||||
AudioStream audioStream = currentInfo.audio_streams.get(ListHelper.getDefaultAudioFormat(activity, currentInfo.audio_streams));
|
||||
AudioStream audioStream = currentInfo.getAudioStreams().get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
|
||||
|
||||
if (activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onAudioPlayed(currentInfo, audioStream);
|
||||
|
@ -792,16 +832,16 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
|
||||
}
|
||||
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
final Intent intent;
|
||||
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
|
||||
if (append) {
|
||||
Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
|
||||
intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue);
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution
|
||||
);
|
||||
activity.startService(intent);
|
||||
}
|
||||
activity.startService(intent);
|
||||
}
|
||||
|
||||
private void openVideoPlayer() {
|
||||
|
@ -820,13 +860,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
|
||||
private void openNormalBackgroundPlayer(final boolean append) {
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
|
||||
if (append) {
|
||||
activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue));
|
||||
Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show();
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue);
|
||||
} else {
|
||||
activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue));
|
||||
Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, itemQueue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,9 +873,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
intent = new Intent();
|
||||
try {
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse(audioStream.url), MediaFormat.getMimeById(audioStream.format));
|
||||
intent.putExtra(Intent.EXTRA_TITLE, currentInfo.name);
|
||||
intent.putExtra("title", currentInfo.name);
|
||||
intent.setDataAndType(Uri.parse(audioStream.getUrl()), audioStream.getFormat().getMimeType());
|
||||
intent.putExtra(Intent.EXTRA_TITLE, currentInfo.getName());
|
||||
intent.putExtra("title", currentInfo.getName());
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -866,19 +904,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
private void openNormalPlayer(VideoStream selectedVideoStream) {
|
||||
Intent mIntent;
|
||||
boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.use_old_player_key), false)
|
||||
|| (Build.VERSION.SDK_INT < 16);
|
||||
boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16);
|
||||
if (!useOldPlayer) {
|
||||
// ExoPlayer
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().getResolution());
|
||||
} else {
|
||||
// Internal Player
|
||||
mIntent = new Intent(activity, PlayVideoActivity.class)
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.name)
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.url)
|
||||
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.start_position);
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.getName())
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.getUrl())
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.getUrl())
|
||||
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.getStartPosition());
|
||||
}
|
||||
startActivity(mIntent);
|
||||
}
|
||||
|
@ -888,9 +925,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
Intent intent = new Intent();
|
||||
try {
|
||||
intent.setAction(Intent.ACTION_VIEW)
|
||||
.setDataAndType(Uri.parse(selectedVideoStream.url), MediaFormat.getMimeById(selectedVideoStream.format))
|
||||
.putExtra(Intent.EXTRA_TITLE, currentInfo.name)
|
||||
.putExtra("title", currentInfo.name);
|
||||
.setDataAndType(Uri.parse(selectedVideoStream.getUrl()), selectedVideoStream.getFormat().getMimeType())
|
||||
.putExtra(Intent.EXTRA_TITLE, currentInfo.getName())
|
||||
.putExtra("title", currentInfo.getName());
|
||||
this.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -1063,20 +1100,28 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
public void handleResult(@NonNull StreamInfo info) {
|
||||
super.handleResult(info);
|
||||
|
||||
setInitialData(info.service_id, info.url, info.name);
|
||||
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
pushToStack(serviceId, url, name);
|
||||
|
||||
animateView(thumbnailPlayButton, true, 200);
|
||||
videoTitleTextView.setText(name);
|
||||
|
||||
if (!TextUtils.isEmpty(info.uploader_name)) uploaderTextView.setText(info.uploader_name);
|
||||
uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader_name) ? View.VISIBLE : View.GONE);
|
||||
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
uploaderTextView.setText(info.getUploaderName());
|
||||
uploaderTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
uploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
|
||||
|
||||
if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(activity, info.view_count));
|
||||
videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
|
||||
if (info.getViewCount() >= 0) {
|
||||
videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount()));
|
||||
videoCountView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
videoCountView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (info.dislike_count == -1 && info.like_count == -1) {
|
||||
if (info.getDislikeCount() == -1 && info.getLikeCount() == -1) {
|
||||
thumbsDownImageView.setVisibility(View.VISIBLE);
|
||||
thumbsUpImageView.setVisibility(View.VISIBLE);
|
||||
thumbsUpTextView.setVisibility(View.GONE);
|
||||
|
@ -1084,14 +1129,23 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
thumbsDisabledTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.shortCount(activity, info.dislike_count));
|
||||
thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
|
||||
thumbsDownImageView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (info.like_count >= 0) thumbsUpTextView.setText(Localization.shortCount(activity, info.like_count));
|
||||
thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
|
||||
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
|
||||
if (info.getDislikeCount() >= 0) {
|
||||
thumbsDownTextView.setText(Localization.shortCount(activity, info.getDislikeCount()));
|
||||
thumbsDownTextView.setVisibility(View.VISIBLE);
|
||||
thumbsDownImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
thumbsDownTextView.setVisibility(View.GONE);
|
||||
thumbsDownImageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (info.getLikeCount() >= 0) {
|
||||
thumbsUpTextView.setText(Localization.shortCount(activity, info.getLikeCount()));
|
||||
thumbsUpTextView.setVisibility(View.VISIBLE);
|
||||
thumbsUpImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
thumbsUpTextView.setVisibility(View.GONE);
|
||||
thumbsUpImageView.setVisibility(View.GONE);
|
||||
}
|
||||
thumbsDisabledTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
@ -1100,10 +1154,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoDescriptionView.setVisibility(View.GONE);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
if (!TextUtils.isEmpty(info.upload_date)) {
|
||||
videoUploadDateView.setText(Localization.localizeDate(activity, info.upload_date));
|
||||
if (!TextUtils.isEmpty(info.getUploadDate())) {
|
||||
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
|
||||
}
|
||||
prepareDescription(info.description);
|
||||
prepareDescription(info.getDescription());
|
||||
|
||||
animateView(spinnerToolbar, true, 500);
|
||||
setupActionBarHandler(info);
|
||||
|
@ -1113,10 +1167,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
toggleExpandRelatedVideos(currentInfo);
|
||||
wasRelatedStreamsExpanded = false;
|
||||
}
|
||||
setTitleToUrl(info.service_id, info.url, info.name);
|
||||
setTitleToUrl(info.getServiceId(), info.getUrl(), info.getName());
|
||||
|
||||
if (!info.errors.isEmpty()) {
|
||||
showSnackBarError(info.errors, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(info.service_id), info.url, 0);
|
||||
if (!info.getErrors().isEmpty()) {
|
||||
showSnackBarError(info.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(info.getServiceId()), info.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (autoPlayEnabled) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.fragments.list;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
@ -19,7 +20,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
|
||||
|
@ -137,7 +140,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openVideoDetailFragment(
|
||||
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
|
||||
selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(StreamInfoItem selectedItem) {
|
||||
showStreamDialog(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -147,8 +155,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openChannelFragment(
|
||||
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
|
||||
selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(ChannelInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() {
|
||||
|
@ -157,8 +168,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
|
||||
selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlaylistInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
|
@ -176,6 +190,33 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
}
|
||||
}
|
||||
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -190,8 +190,8 @@ public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListF
|
|||
public void handleResult(@NonNull I result) {
|
||||
super.handleResult(result);
|
||||
|
||||
url = result.url;
|
||||
name = result.name;
|
||||
url = result.getUrl();
|
||||
name = result.getName();
|
||||
setTitle(name);
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package org.schabi.newpipe.fragments.list.channel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -10,6 +12,7 @@ import android.support.v4.content.ContextCompat;
|
|||
import android.support.v7.app.ActionBar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
@ -18,7 +21,9 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
|
||||
|
@ -28,12 +33,19 @@ import org.schabi.newpipe.extractor.ListExtractor;
|
|||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.fragments.subscription.SubscriptionService;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.playlist.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -68,6 +80,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
private TextView headerTitleView;
|
||||
private TextView headerSubscribersTextView;
|
||||
private Button headerSubscribeButton;
|
||||
private View playlistCtrl;
|
||||
|
||||
private LinearLayout headerPlayAllButton;
|
||||
private LinearLayout headerPopupButton;
|
||||
private LinearLayout headerBackgroundButton;
|
||||
|
||||
private MenuItem menuRssButton;
|
||||
|
||||
|
@ -88,7 +105,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
&& useAsFrontPage
|
||||
&& isVisibleToUser) {
|
||||
try {
|
||||
activity.getSupportActionBar().setTitle(currentInfo.name);
|
||||
activity.getSupportActionBar().setTitle(currentInfo.getName());
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
|
@ -124,10 +141,57 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
|
||||
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
||||
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||
|
||||
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
|
||||
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -144,7 +208,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||
menuRssButton = menu.findItem(R.id.menu_item_rss);
|
||||
if (currentInfo != null) {
|
||||
menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.feed_url));
|
||||
menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.getFeedUrl()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -153,7 +217,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
private void openRssFeed() {
|
||||
final ChannelInfo info = currentInfo;
|
||||
if(info != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.feed_url));
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
@ -200,12 +264,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
animateView(headerSubscribeButton, false, 100);
|
||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.service_id), "Get subscription status", 0);
|
||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0);
|
||||
}
|
||||
};
|
||||
|
||||
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
|
||||
.getSubscription(info.service_id, info.url)
|
||||
.getSubscription(info.getServiceId(), info.getUrl())
|
||||
.toObservable();
|
||||
|
||||
disposables.add(observable
|
||||
|
@ -251,14 +315,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
final Action onComplete = new Action() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.url);
|
||||
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
|
||||
}
|
||||
};
|
||||
|
||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.service_id), "Updating Subscription for " + info.url, R.string.subscription_update_failed);
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -279,7 +343,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.service_id), "Subscription Change", R.string.subscription_change_failed);
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -303,9 +367,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
if (subscriptionEntities.isEmpty()) {
|
||||
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
||||
SubscriptionEntity channel = new SubscriptionEntity();
|
||||
channel.setServiceId(info.service_id);
|
||||
channel.setUrl(info.url);
|
||||
channel.setData(info.name, info.avatar_url, info.description, info.subscriber_count);
|
||||
channel.setServiceId(info.getServiceId());
|
||||
channel.setUrl(info.getUrl());
|
||||
channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
|
||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
||||
|
@ -376,29 +440,70 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
imageLoader.displayImage(result.banner_url, headerChannelBanner, DISPLAY_BANNER_OPTIONS);
|
||||
imageLoader.displayImage(result.avatar_url, headerAvatarView, DISPLAY_AVATAR_OPTIONS);
|
||||
|
||||
if (result.subscriber_count != -1) {
|
||||
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.subscriber_count));
|
||||
if (result.getSubscriberCount() != -1) {
|
||||
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
|
||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||
} else headerSubscribersTextView.setVisibility(View.GONE);
|
||||
|
||||
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.feed_url));
|
||||
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||
playlistCtrl.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.service_id), result.url, 0);
|
||||
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
||||
updateSubscription(result);
|
||||
monitorSubscription(result);
|
||||
|
||||
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
headerPopupButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
|
||||
Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG);
|
||||
TextView messageView = toast.getView().findViewById(android.R.id.message);
|
||||
if (messageView != null) messageView.setGravity(Gravity.CENTER);
|
||||
toast.show();
|
||||
return;
|
||||
}
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
return getPlayQueue(0);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
return new ChannelPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextStreamsUrl(),
|
||||
infoListAdapter.getItemsList(),
|
||||
index
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.NextItemsResult result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url, R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,12 +297,12 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
|||
// Called only when response is non-empty
|
||||
@Override
|
||||
public void onSuccess(final ChannelInfo channelInfo) {
|
||||
if (infoListAdapter == null || channelInfo.related_streams.isEmpty()) {
|
||||
if (infoListAdapter == null || channelInfo.getRelatedStreams().isEmpty()) {
|
||||
onDone();
|
||||
return;
|
||||
}
|
||||
|
||||
final InfoItem item = channelInfo.related_streams.get(0);
|
||||
final InfoItem item = channelInfo.getRelatedStreams().get(0);
|
||||
// Keep requesting new items if the current one already exists
|
||||
boolean itemExists = doesItemExist(infoListAdapter.getItemsList(), item);
|
||||
if (!itemExists) {
|
||||
|
@ -412,9 +412,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
|||
private boolean doesItemExist(final List<InfoItem> items, final InfoItem item) {
|
||||
for (final InfoItem existingItem : items) {
|
||||
if (existingItem.info_type == item.info_type &&
|
||||
existingItem.service_id == item.service_id &&
|
||||
existingItem.name.equals(item.name) &&
|
||||
existingItem.url.equals(item.url)) return true;
|
||||
existingItem.getServiceId() == item.getServiceId() &&
|
||||
existingItem.getName().equals(item.getName()) &&
|
||||
existingItem.getUrl().equals(item.getUrl())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
|||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.Single;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
@ -53,7 +54,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|||
|
||||
public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
private String kioskId = "";
|
||||
@State
|
||||
protected String kioskId = "";
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -87,21 +89,40 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedState) {
|
||||
super.onActivityCreated(savedState);
|
||||
try {
|
||||
activity.getSupportActionBar()
|
||||
.setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity()));
|
||||
} catch (Exception e) {
|
||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
||||
"none",
|
||||
"none", R.string.app_ui_crash);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if(useAsFrontPage && isVisibleToUser) {
|
||||
if(useAsFrontPage && isVisibleToUser && activity != null) {
|
||||
try {
|
||||
activity.getSupportActionBar().setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity()));
|
||||
activity.getSupportActionBar()
|
||||
.setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity()));
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
||||
"none",
|
||||
"none", R.string.app_ui_crash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
||||
View view = inflater.inflate(R.layout.fragment_kiosk, container, false);
|
||||
activity.getSupportActionBar()
|
||||
.setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity()));
|
||||
return view;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -153,10 +174,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
supportActionBar.setTitle(title);
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors,
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_PLAYLIST,
|
||||
NewPipe.getNameOfService(result.service_id), result.url, 0);
|
||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,8 +185,8 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
public void handleNextItems(ListExtractor.NextItemsResult result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors,
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
|
||||
, "Get next page of: " + url, 0);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.fragments.list.playlist;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
@ -13,7 +14,6 @@ import android.view.Menu;
|
|||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
@ -23,12 +23,12 @@ import org.schabi.newpipe.extractor.ListExtractor;
|
|||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
@ -50,10 +50,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
private TextView headerUploaderName;
|
||||
private ImageView headerUploaderAvatar;
|
||||
private TextView headerStreamCount;
|
||||
private View playlistCtrl;
|
||||
|
||||
private Button headerPlayAllButton;
|
||||
private Button headerPopupButton;
|
||||
private Button headerBackgroundButton;
|
||||
private View headerPlayAllButton;
|
||||
private View headerPopupButton;
|
||||
private View headerBackgroundButton;
|
||||
|
||||
public static PlaylistFragment getInstance(int serviceId, String url, String name) {
|
||||
PlaylistFragment instance = new PlaylistFragment();
|
||||
|
@ -81,10 +82,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
|
||||
headerUploaderAvatar = headerRootLayout.findViewById(R.id.uploader_avatar_view);
|
||||
headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
|
||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_play_popup_button);
|
||||
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_play_bg_button);
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
|
||||
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
@ -103,6 +105,47 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
inflater.inflate(R.menu.menu_playlist, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Load and handle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -138,29 +181,31 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
animateView(headerRootLayout, true, 100);
|
||||
animateView(headerUploaderLayout, true, 300);
|
||||
headerUploaderLayout.setOnClickListener(null);
|
||||
if (!TextUtils.isEmpty(result.uploader_name)) {
|
||||
headerUploaderName.setText(result.uploader_name);
|
||||
if (!TextUtils.isEmpty(result.uploader_url)) {
|
||||
if (!TextUtils.isEmpty(result.getUploaderName())) {
|
||||
headerUploaderName.setText(result.getUploaderName());
|
||||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||
headerUploaderLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(), result.service_id, result.uploader_url, result.uploader_name);
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(), result.getServiceId(), result.getUploaderUrl(), result.getUploaderName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
imageLoader.displayImage(result.uploader_avatar_url, headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
|
||||
playlistCtrl.setVisibility(View.VISIBLE);
|
||||
|
||||
imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
|
||||
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.stream_count, (int) result.stream_count));
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.service_id), result.url, 0);
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(buildPlaylistIntent(MainVideoPlayer.class));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
headerPopupButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -173,34 +218,37 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
toast.show();
|
||||
return;
|
||||
}
|
||||
activity.startService(buildPlaylistIntent(PopupVideoPlayer.class));
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
activity.startService(buildPlaylistIntent(BackgroundPlayer.class));
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Intent buildPlaylistIntent(final Class targetClazz) {
|
||||
final PlayQueue playQueue = new ExternalPlayQueue(
|
||||
currentInfo.service_id,
|
||||
currentInfo.url,
|
||||
currentInfo.next_streams_url,
|
||||
private PlayQueue getPlayQueue() {
|
||||
return getPlayQueue(0);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
return new PlaylistPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextStreamsUrl(),
|
||||
infoListAdapter.getItemsList(),
|
||||
0
|
||||
index
|
||||
);
|
||||
return NavigationHelper.getPlayerIntent(activity, targetClazz, playQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.NextItemsResult result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
|
||||
, "Get next page of: " + url, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
suggestionListAdapter.setShowSugestinHistory(isSearchHistoryEnabled);
|
||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||
|
||||
searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO();
|
||||
}
|
||||
|
@ -446,6 +446,12 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
searchEditText.setText(item.query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionItemInserted(SuggestionItem item) {
|
||||
searchEditText.setText(item.query);
|
||||
searchEditText.setSelection(searchEditText.getText().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionItemLongClick(SuggestionItem item) {
|
||||
if (item.fromHistory) showDeleteSuggestionDialog(item);
|
||||
|
@ -855,8 +861,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
lastSearchedQuery = searchQuery;
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (result.resultList.size() > 0) {
|
||||
infoListAdapter.addInfoItemList(result.resultList);
|
||||
if (!result.getResults().isEmpty()) {
|
||||
infoListAdapter.addInfoItemList(result.getResults());
|
||||
} else {
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
|
@ -870,11 +876,11 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
@Override
|
||||
public void handleNextItems(ListExtractor.NextItemsResult result) {
|
||||
showListFooter(false);
|
||||
currentPage = Integer.parseInt(result.nextItemsUrl);
|
||||
infoListAdapter.addInfoItemList(result.nextItemsList);
|
||||
currentPage = Integer.parseInt(result.getNextItemsUrl());
|
||||
infoListAdapter.addInfoItemList(result.getNextItemsList());
|
||||
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
|
||||
, "\"" + searchQuery + "\" → page " + currentPage, 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
|
|
|
@ -19,10 +19,11 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
private final ArrayList<SuggestionItem> items = new ArrayList<>();
|
||||
private final Context context;
|
||||
private OnSuggestionItemSelected listener;
|
||||
private boolean showSugestinHistory = true;
|
||||
private boolean showSuggestionHistory = true;
|
||||
|
||||
public interface OnSuggestionItemSelected {
|
||||
void onSuggestionItemSelected(SuggestionItem item);
|
||||
void onSuggestionItemInserted(SuggestionItem item);
|
||||
void onSuggestionItemLongClick(SuggestionItem item);
|
||||
}
|
||||
|
||||
|
@ -32,7 +33,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
|
||||
public void setItems(List<SuggestionItem> items) {
|
||||
this.items.clear();
|
||||
if (showSugestinHistory) {
|
||||
if (showSuggestionHistory) {
|
||||
this.items.addAll(items);
|
||||
} else {
|
||||
// remove history items if history is disabled
|
||||
|
@ -49,8 +50,8 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setShowSugestinHistory(boolean v) {
|
||||
showSugestinHistory = v;
|
||||
public void setShowSuggestionHistory(boolean v) {
|
||||
showSuggestionHistory = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,19 +63,25 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
|
||||
final SuggestionItem currentItem = getItem(position);
|
||||
holder.updateFrom(currentItem);
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
holder.queryView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (listener != null) listener.onSuggestionItemSelected(currentItem);
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
holder.queryView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (listener != null) listener.onSuggestionItemLongClick(currentItem);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
holder.insertView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (listener != null) listener.onSuggestionItemInserted(currentItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private SuggestionItem getItem(int position) {
|
||||
|
@ -93,6 +100,8 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
public static class SuggestionItemHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView itemSuggestionQuery;
|
||||
private final ImageView suggestionIcon;
|
||||
private final View queryView;
|
||||
private final View insertView;
|
||||
|
||||
// Cache some ids, as they can potentially be constantly updated/recycled
|
||||
private final int historyResId;
|
||||
|
@ -103,6 +112,9 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
|||
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
|
||||
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
|
||||
|
||||
queryView = rootView.findViewById(R.id.suggestion_search);
|
||||
insertView = rootView.findViewById(R.id.suggestion_insert);
|
||||
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
|
|||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -131,9 +129,12 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
|||
@Override
|
||||
public void selected(ChannelInfoItem selectedItem) {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(ChannelInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
headerRootLayout.setOnClickListener(new View.OnClickListener() {
|
||||
|
|
|
@ -112,7 +112,7 @@ public class SubscriptionService {
|
|||
// Subscriber count changes very often, making this check almost unnecessary.
|
||||
// Consider removing it later.
|
||||
if (!isSubscriptionUpToDate(info, subscription)) {
|
||||
subscription.setData(info.name, info.avatar_url, info.description, info.subscriber_count);
|
||||
subscription.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
|
||||
|
||||
return update(subscription);
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ public class SubscriptionService {
|
|||
}
|
||||
};
|
||||
|
||||
return subscriptionTable().getSubscription(info.service_id, info.url)
|
||||
return subscriptionTable().getSubscription(info.getServiceId(), info.getUrl())
|
||||
.firstOrError()
|
||||
.flatMapCompletable(update);
|
||||
}
|
||||
|
@ -137,11 +137,11 @@ public class SubscriptionService {
|
|||
}
|
||||
|
||||
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
|
||||
return info.url.equals(entity.getUrl()) &&
|
||||
info.service_id == entity.getServiceId() &&
|
||||
info.name.equals(entity.getName()) &&
|
||||
info.avatar_url.equals(entity.getAvatarUrl()) &&
|
||||
info.description.equals(entity.getDescription()) &&
|
||||
info.subscriber_count == entity.getSubscriberCount();
|
||||
return info.getUrl().equals(entity.getUrl()) &&
|
||||
info.getServiceId() == entity.getServiceId() &&
|
||||
info.getName().equals(entity.getName()) &&
|
||||
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
|
||||
info.getDescription().equals(entity.getDescription()) &&
|
||||
info.getSubscriberCount() == entity.getSubscriberCount();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ public class InfoItemBuilder {
|
|||
|
||||
public interface OnInfoItemSelectedListener<T extends InfoItem> {
|
||||
void selected(T selectedItem);
|
||||
void held(T selectedItem);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
public class InfoItemDialog {
|
||||
private final AlertDialog dialog;
|
||||
|
||||
public InfoItemDialog(@NonNull final Activity activity,
|
||||
@NonNull final StreamInfoItem info,
|
||||
@NonNull final String[] commands,
|
||||
@NonNull final DialogInterface.OnClickListener actions) {
|
||||
this(activity, commands, actions, info.getName(), info.uploader_name);
|
||||
}
|
||||
|
||||
public InfoItemDialog(@NonNull final Activity activity,
|
||||
@NonNull final String[] commands,
|
||||
@NonNull final DialogInterface.OnClickListener actions,
|
||||
@NonNull final String title,
|
||||
@Nullable final String additionalDetail) {
|
||||
|
||||
final LayoutInflater inflater = activity.getLayoutInflater();
|
||||
final View bannerView = inflater.inflate(R.layout.dialog_title, null);
|
||||
bannerView.setSelected(true);
|
||||
|
||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
titleView.setText(title);
|
||||
|
||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
if (additionalDetail != null) {
|
||||
detailsView.setText(additionalDetail);
|
||||
detailsView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
detailsView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
dialog = new AlertDialog.Builder(activity)
|
||||
.setCustomTitle(bannerView)
|
||||
.setItems(commands, actions)
|
||||
.create();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
dialog.show();
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
|||
if (!(infoItem instanceof ChannelInfoItem)) return;
|
||||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.name);
|
||||
itemTitleView.setText(item.getName());
|
||||
itemAdditionalDetailView.setText(getDetailLine(item));
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
|
|
|
@ -32,7 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder {
|
|||
if (!(infoItem instanceof PlaylistInfoItem)) return;
|
||||
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.name);
|
||||
itemTitleView.setText(item.getName());
|
||||
itemStreamCountView.setText(item.stream_count + "");
|
||||
itemUploaderView.setText(item.uploader_name);
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||
if (!(infoItem instanceof StreamInfoItem)) return;
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
|
||||
itemVideoTitleView.setText(item.name);
|
||||
itemVideoTitleView.setText(item.getName());
|
||||
itemUploaderView.setText(item.uploader_name);
|
||||
|
||||
if (item.duration > 0) {
|
||||
|
@ -67,6 +67,38 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
switch (item.stream_type) {
|
||||
case AUDIO_STREAM:
|
||||
case VIDEO_STREAM:
|
||||
case FILE:
|
||||
enableLongClick(item);
|
||||
break;
|
||||
case LIVE_STREAM:
|
||||
case AUDIO_LIVE_STREAM:
|
||||
case NONE:
|
||||
default:
|
||||
disableLongClick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void enableLongClick(final StreamInfoItem item) {
|
||||
itemView.setLongClickable(true);
|
||||
itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
||||
itemBuilder.getOnStreamSelectedListener().held(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void disableLongClick() {
|
||||
itemView.setLongClickable(false);
|
||||
itemView.setOnLongClickListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
|
|||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
||||
|
@ -68,6 +69,10 @@ public final class BackgroundPlayer extends Service {
|
|||
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 BasePlayerImpl basePlayerImpl;
|
||||
private LockManager lockManager;
|
||||
|
@ -130,16 +135,6 @@ public final class BackgroundPlayer extends Service {
|
|||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void openControl(final Context context) {
|
||||
Intent intent = new Intent(context, BackgroundPlayerActivity.class);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
if (DEBUG) Log.d(TAG, "onClose() called");
|
||||
|
||||
|
@ -182,7 +177,7 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCustomContentView(notRemoteView)
|
||||
.setCustomBigContentView(bigNotRemoteView);
|
||||
|
@ -191,6 +186,8 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
private void setupNotification(RemoteViews remoteViews) {
|
||||
if (basePlayerImpl == null) return;
|
||||
|
||||
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
|
||||
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
|
||||
|
||||
|
@ -203,10 +200,21 @@ public final class BackgroundPlayer extends Service {
|
|||
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
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));
|
||||
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());
|
||||
}
|
||||
|
@ -241,17 +249,15 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
|
||||
final String methodName = "setImageResource";
|
||||
|
||||
switch (repeatMode) {
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_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, methodName, R.drawable.exo_controls_repeat_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, methodName, R.drawable.exo_controls_repeat_all);
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -372,6 +378,7 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
super.sync(item, info);
|
||||
|
||||
resetNotification();
|
||||
|
@ -380,12 +387,13 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
|
||||
if (index < 0) return null;
|
||||
if (index < 0 || index >= info.audio_streams.size()) return null;
|
||||
|
||||
final AudioStream audio = info.audio_streams.get(index);
|
||||
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
return buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.format));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -449,6 +457,8 @@ public final class BackgroundPlayer extends Service {
|
|||
intentFilter.addAction(ACTION_REPEAT);
|
||||
intentFilter.addAction(ACTION_PLAY_PREVIOUS);
|
||||
intentFilter.addAction(ACTION_PLAY_NEXT);
|
||||
intentFilter.addAction(ACTION_FAST_REWIND);
|
||||
intentFilter.addAction(ACTION_FAST_FORWARD);
|
||||
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
|
@ -469,7 +479,7 @@ public final class BackgroundPlayer extends Service {
|
|||
onVideoPlayPause();
|
||||
break;
|
||||
case ACTION_OPEN_CONTROLS:
|
||||
openControl(getApplicationContext());
|
||||
NavigationHelper.openBackgroundPlayerControl(getApplicationContext());
|
||||
break;
|
||||
case ACTION_REPEAT:
|
||||
onRepeatClicked();
|
||||
|
@ -480,6 +490,12 @@ public final class BackgroundPlayer extends Service {
|
|||
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;
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.content.IntentFilter;
|
|||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
@ -76,7 +77,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Predicate;
|
||||
|
@ -134,6 +134,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
|
||||
protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
|
||||
protected final static int PROGRESS_LOOP_INTERVAL = 500;
|
||||
protected final static int RECOVERY_SKIP_THRESHOLD = 3000; // 3 seconds
|
||||
|
||||
protected SimpleExoPlayer simpleExoPlayer;
|
||||
protected AudioReactor audioReactor;
|
||||
|
@ -193,7 +194,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.filter(new Predicate<Long>() {
|
||||
@Override
|
||||
public boolean test(@NonNull Long aLong) throws Exception {
|
||||
public boolean test(Long aLong) throws Exception {
|
||||
return isProgressLoopRunning();
|
||||
}
|
||||
})
|
||||
|
@ -235,7 +236,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
initPlayback(queue);
|
||||
}
|
||||
|
||||
protected void initPlayback(@NonNull final PlayQueue queue) {
|
||||
protected void initPlayback(final PlayQueue queue) {
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
playbackManager = new MediaSourceManager(this, playQueue);
|
||||
|
@ -453,16 +454,20 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
final PlayQueueItem currentSourceItem = playQueue.getItem();
|
||||
|
||||
// Check if already playing correct window
|
||||
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
|
||||
final boolean isCurrentWindowCorrect =
|
||||
simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
|
||||
|
||||
// Check if recovering
|
||||
if (isCurrentWindowCorrect && currentSourceItem != null &&
|
||||
currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||
if (isCurrentWindowCorrect && currentSourceItem != null) {
|
||||
/* Recovering with sub-second position may cause a long buffer delay in ExoPlayer,
|
||||
* rounding this position to the nearest second will help alleviate this.*/
|
||||
final long position = currentSourceItem.getRecoveryPosition();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)position));
|
||||
/* Skip recovering if the recovery position is not set.*/
|
||||
if (position == PlayQueueItem.RECOVERY_UNSET) return;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex +
|
||||
" at: " + getTimeString((int)position));
|
||||
simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
|
||||
playQueue.unsetRecovery(currentSourceIndex);
|
||||
}
|
||||
|
@ -515,7 +520,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
break;
|
||||
case Player.STATE_READY: //3
|
||||
recover();
|
||||
|
||||
if (!isPrepared) {
|
||||
isPrepared = true;
|
||||
onPrepared(playWhenReady);
|
||||
|
@ -545,14 +549,18 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
* an error to the play queue based on if the current error can be skipped.
|
||||
*
|
||||
* This is done because ExoPlayer reports the source exceptions before window is
|
||||
* transitioned on seamless playback.
|
||||
* transitioned on seamless playback. Because player error causes ExoPlayer to go
|
||||
* back to {@link Player#STATE_IDLE STATE_IDLE}, we reset and prepare the media source
|
||||
* again to resume playback.
|
||||
*
|
||||
* Because player error causes ExoPlayer to go back to {@link Player#STATE_IDLE STATE_IDLE},
|
||||
* we reset and prepare the media source again to resume playback.<br><br>
|
||||
* In the event that this error is produced during a valid stream playback, we save the
|
||||
* current position so the playback may be recovered and resumed manually by the user. This
|
||||
* happens only if the playback is {@link #RECOVERY_SKIP_THRESHOLD} milliseconds until complete.
|
||||
* <br><br>
|
||||
*
|
||||
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
|
||||
* If a runtime error occurred, then we can try to recover it by restarting the playback
|
||||
* after setting the timestamp recovery.
|
||||
* after setting the timestamp recovery. <br><br>
|
||||
*
|
||||
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
|
||||
* If the renderer failed, treat the error as unrecoverable.
|
||||
|
@ -569,6 +577,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
|
||||
switch (error.type) {
|
||||
case ExoPlaybackException.TYPE_SOURCE:
|
||||
if (simpleExoPlayer.getCurrentPosition() <
|
||||
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD) {
|
||||
setRecovery();
|
||||
}
|
||||
playQueue.error(isCurrentWindowValid());
|
||||
showStreamError(error);
|
||||
break;
|
||||
|
@ -591,12 +603,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]");
|
||||
|
||||
// If the user selects a new track, then the discontinuity occurs after the index is changed.
|
||||
// Therefore, the only source that causes a discrepancy would be autoplay,
|
||||
// Therefore, the only source that causes a discrepancy would be gapless transition,
|
||||
// which can only offset the current track by +1.
|
||||
if (newWindowIndex != playQueue.getIndex() && playbackManager != null) {
|
||||
if (newWindowIndex == playQueue.getIndex() + 1) {
|
||||
playQueue.offsetIndex(+1);
|
||||
playbackManager.load();
|
||||
}
|
||||
playbackManager.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -613,6 +625,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Blocking...");
|
||||
|
||||
currentItem = null;
|
||||
currentInfo = null;
|
||||
simpleExoPlayer.stop();
|
||||
isPrepared = false;
|
||||
|
||||
|
@ -631,17 +645,21 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sync(@android.support.annotation.NonNull final PlayQueueItem item,
|
||||
public void sync(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Syncing...");
|
||||
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
currentItem = item;
|
||||
currentInfo = info;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Syncing...");
|
||||
if (simpleExoPlayer == null) return;
|
||||
|
||||
// Check if on wrong window
|
||||
final int currentSourceIndex = playQueue.getIndex();
|
||||
if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) {
|
||||
final int currentSourceIndex = playQueue.indexOf(item);
|
||||
if (currentSourceIndex != playQueue.getIndex()) {
|
||||
Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
|
||||
"], queue index=[" + playQueue.getIndex() + "]");
|
||||
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
|
||||
final long startPos = info != null ? info.start_position : 0;
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
|
||||
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
|
||||
|
@ -756,10 +774,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
} else {
|
||||
playQueue.setIndex(index);
|
||||
}
|
||||
|
||||
if (!isPlaying()) {
|
||||
onVideoPlayPause();
|
||||
}
|
||||
}
|
||||
|
||||
public void seekBy(int milliSeconds) {
|
||||
|
@ -826,15 +840,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
public String getVideoUrl() {
|
||||
return currentItem == null ? null : currentItem.getUrl();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
|
||||
}
|
||||
|
||||
public String getVideoTitle() {
|
||||
return currentItem == null ? null : currentItem.getTitle();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
|
||||
}
|
||||
|
||||
public String getUploaderName() {
|
||||
return currentItem == null ? null : currentItem.getUploader();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
|
@ -870,8 +884,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
|
||||
if (simpleExoPlayer == null) return defaultParameters;
|
||||
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
|
||||
return parameters == null ? new PlaybackParameters(1f, 1f) : parameters;
|
||||
return parameters == null ? defaultParameters : parameters;
|
||||
}
|
||||
|
||||
public void setPlaybackParameters(float speed, float pitch) {
|
||||
|
@ -900,7 +916,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
final int queuePos = playQueue.getIndex();
|
||||
final long windowPos = simpleExoPlayer.getCurrentPosition();
|
||||
|
||||
setRecovery(queuePos, windowPos);
|
||||
if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
|
||||
setRecovery(queuePos, windowPos);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRecovery(final int queuePos, final long windowPos) {
|
||||
|
|
|
@ -22,17 +22,22 @@ package org.schabi.newpipe.player;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
@ -48,6 +53,7 @@ import com.google.android.exoplayer2.Player;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
|
@ -58,6 +64,8 @@ import org.schabi.newpipe.util.NavigationHelper;
|
|||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
@ -76,6 +84,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
private boolean activityPaused;
|
||||
private VideoPlayerImpl playerImpl;
|
||||
|
||||
private SharedPreferences defaultPreferences;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Activity LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -84,6 +94,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
@ -94,6 +105,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
showSystemUi();
|
||||
setContentView(R.layout.activity_main_player);
|
||||
playerImpl = new VideoPlayerImpl(this);
|
||||
|
@ -141,6 +154,11 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
activityPaused = false;
|
||||
}
|
||||
if(globalScreenOrientationLocked()) {
|
||||
boolean lastOrientationWasLandscape
|
||||
= defaultPreferences.getBoolean(getString(R.string.last_orientation_landscape_key), false);
|
||||
setLandScape(lastOrientationWasLandscape);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,6 +168,17 @@ public final class MainVideoPlayer extends Activity {
|
|||
if (playerImpl != null) playerImpl.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (playerImpl.isSomePopupMenuVisible()) {
|
||||
playerImpl.moreOptionsPopupMenu.dismiss();
|
||||
playerImpl.getQualityPopupMenu().dismiss();
|
||||
playerImpl.getPlaybackSpeedPopupMenu().dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -182,11 +211,28 @@ public final class MainVideoPlayer extends Activity {
|
|||
}
|
||||
|
||||
private void toggleOrientation() {
|
||||
setRequestedOrientation(getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels
|
||||
setLandScape(!isLandScape());
|
||||
defaultPreferences.edit()
|
||||
.putBoolean(getString(R.string.last_orientation_landscape_key), !isLandScape())
|
||||
.apply();
|
||||
}
|
||||
|
||||
private boolean isLandScape() {
|
||||
return getResources().getDisplayMetrics().heightPixels < getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
|
||||
private void setLandScape(boolean v) {
|
||||
setRequestedOrientation(v
|
||||
? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
: ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
|
||||
}
|
||||
|
||||
private boolean globalScreenOrientationLocked() {
|
||||
// 1: Screen orientation changes using acelerometer
|
||||
// 0: Screen orientatino is locked
|
||||
return !(android.provider.Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1);
|
||||
}
|
||||
|
||||
protected void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
|
@ -222,7 +268,6 @@ public final class MainVideoPlayer extends Activity {
|
|||
private ImageButton repeatButton;
|
||||
private ImageButton shuffleButton;
|
||||
|
||||
private ImageButton screenRotationButton;
|
||||
private ImageButton playPauseButton;
|
||||
private ImageButton playPreviousButton;
|
||||
private ImageButton playNextButton;
|
||||
|
@ -234,6 +279,10 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
private boolean queueVisible;
|
||||
|
||||
private ImageButton moreOptionsButton;
|
||||
public int moreOptionsPopupMenuGroupId = 89;
|
||||
public PopupMenu moreOptionsPopupMenu;
|
||||
|
||||
VideoPlayerImpl(final Context context) {
|
||||
super("VideoPlayerImpl" + MainVideoPlayer.TAG, context);
|
||||
}
|
||||
|
@ -249,10 +298,12 @@ public final class MainVideoPlayer extends Activity {
|
|||
this.repeatButton = rootView.findViewById(R.id.repeatButton);
|
||||
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
|
||||
|
||||
this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton);
|
||||
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
|
||||
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = rootView.findViewById(R.id.playNextButton);
|
||||
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
|
||||
this.moreOptionsPopupMenu = new PopupMenu(context, moreOptionsButton);
|
||||
this.moreOptionsPopupMenu.getMenuInflater().inflate(R.menu.menu_videooptions, moreOptionsPopupMenu.getMenu());
|
||||
|
||||
titleTextView.setSelected(true);
|
||||
channelTextView.setSelected(true);
|
||||
|
@ -276,7 +327,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
playPauseButton.setOnClickListener(this);
|
||||
playPreviousButton.setOnClickListener(this);
|
||||
playNextButton.setOnClickListener(this);
|
||||
screenRotationButton.setOnClickListener(this);
|
||||
moreOptionsButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -348,6 +399,28 @@ public final class MainVideoPlayer extends Activity {
|
|||
finish();
|
||||
}
|
||||
|
||||
public void onPlayBackgroundButtonClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onPlayBackgroundButtonClicked() called");
|
||||
if (playerImpl.getPlayer() == null) return;
|
||||
|
||||
setRecovery();
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
BackgroundPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
context.startService(intent);
|
||||
|
||||
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
|
||||
destroy();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
super.onClick(v);
|
||||
|
@ -360,9 +433,6 @@ public final class MainVideoPlayer extends Activity {
|
|||
} else if (v.getId() == playNextButton.getId()) {
|
||||
onPlayNext();
|
||||
|
||||
} else if (v.getId() == screenRotationButton.getId()) {
|
||||
onScreenRotationClicked();
|
||||
|
||||
} else if (v.getId() == queueButton.getId()) {
|
||||
onQueueClicked();
|
||||
return;
|
||||
|
@ -372,6 +442,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
} else if (v.getId() == shuffleButton.getId()) {
|
||||
onShuffleClicked();
|
||||
return;
|
||||
} else if (v.getId() == moreOptionsButton.getId()) {
|
||||
onMoreOptionsClicked();
|
||||
}
|
||||
|
||||
if (getCurrentState() != STATE_COMPLETED) {
|
||||
|
@ -397,7 +469,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
getControlsRoot().setVisibility(View.INVISIBLE);
|
||||
queueLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
itemsList.smoothScrollToPosition(playQueue.getIndex());
|
||||
itemsList.scrollToPosition(playQueue.getIndex());
|
||||
}
|
||||
|
||||
private void onQueueClosed() {
|
||||
|
@ -405,6 +477,32 @@ public final class MainVideoPlayer extends Activity {
|
|||
queueVisible = false;
|
||||
}
|
||||
|
||||
private void onMoreOptionsClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called");
|
||||
buildMoreOptionsMenu();
|
||||
|
||||
try {
|
||||
Field[] fields = moreOptionsPopupMenu.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if ("mPopup".equals(field.getName())) {
|
||||
field.setAccessible(true);
|
||||
Object menuPopupHelper = field.get(moreOptionsPopupMenu);
|
||||
Class<?> classPopupHelper = Class.forName(menuPopupHelper
|
||||
.getClass().getName());
|
||||
Method setForceIcons = classPopupHelper.getMethod(
|
||||
"setForceShowIcon", boolean.class);
|
||||
setForceIcons.invoke(menuPopupHelper, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
moreOptionsPopupMenu.show();
|
||||
isSomePopupMenuVisible = true;
|
||||
showControls(300);
|
||||
}
|
||||
|
||||
private void onScreenRotationClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
|
||||
toggleOrientation();
|
||||
|
@ -555,6 +653,27 @@ public final class MainVideoPlayer extends Activity {
|
|||
setShuffleButton(shuffleButton, playQueue.isShuffled());
|
||||
}
|
||||
|
||||
private void buildMoreOptionsMenu() {
|
||||
if (moreOptionsPopupMenu == null) return;
|
||||
moreOptionsPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.toggleOrientation:
|
||||
onScreenRotationClicked();
|
||||
break;
|
||||
case R.id.switchPopup:
|
||||
onFullScreenButtonClicked();
|
||||
break;
|
||||
case R.id.switchBackground:
|
||||
onPlayBackgroundButtonClicked();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buildQueue() {
|
||||
queueLayout = findViewById(R.id.playQueuePanel);
|
||||
|
||||
|
@ -565,6 +684,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
itemsList.setClickable(true);
|
||||
itemsList.setLongClickable(true);
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(getQueueScrollListener());
|
||||
|
||||
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(itemsList);
|
||||
|
||||
|
@ -578,6 +700,19 @@ public final class MainVideoPlayer extends Activity {
|
|||
});
|
||||
}
|
||||
|
||||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
if (playQueue != null && !playQueue.isComplete()) {
|
||||
playQueue.fetch();
|
||||
} else if (itemsList != null) {
|
||||
itemsList.clearOnScrollListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
|
||||
@Override
|
||||
|
@ -785,4 +920,4 @@ public final class MainVideoPlayer extends Activity {
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import android.view.Gravity;
|
|||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.SeekBar;
|
||||
|
@ -65,6 +66,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
@ -96,7 +98,6 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|||
public final class PopupVideoPlayer extends Service {
|
||||
private static final String TAG = ".PopupVideoPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
private static final int SHUTDOWN_FLING_VELOCITY = 10000;
|
||||
|
||||
private static final int NOTIFICATION_ID = 40028922;
|
||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
||||
|
@ -112,6 +113,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
private WindowManager.LayoutParams windowLayoutParams;
|
||||
private GestureDetector gestureDetector;
|
||||
|
||||
private int shutdownFlingVelocity;
|
||||
private int tossFlingVelocity;
|
||||
|
||||
private float screenWidth, screenHeight;
|
||||
private float popupWidth, popupHeight;
|
||||
|
||||
|
@ -211,12 +215,14 @@ public final class PopupVideoPlayer extends Service {
|
|||
View rootView = View.inflate(this, R.layout.player_popup, null);
|
||||
playerImpl.setup(rootView);
|
||||
|
||||
shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
|
||||
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
|
||||
|
||||
updateScreenSize();
|
||||
|
||||
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this);
|
||||
final float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true);
|
||||
|
||||
float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
|
||||
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
@ -270,7 +276,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_play_arrow_white)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContent(notRemoteView);
|
||||
}
|
||||
|
@ -313,15 +319,6 @@ public final class PopupVideoPlayer extends Service {
|
|||
stopSelf();
|
||||
}
|
||||
|
||||
public void openControl(final Context context) {
|
||||
Intent intent = new Intent(context, PopupVideoPlayerActivity.class);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -366,6 +363,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
private void updatePopupSize(int width, int height) {
|
||||
if (playerImpl == null) return;
|
||||
if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
|
||||
|
||||
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
|
||||
|
@ -404,6 +402,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
protected class VideoPlayerImpl extends VideoPlayer {
|
||||
private TextView resizingIndicator;
|
||||
private ImageButton fullScreenButton;
|
||||
|
||||
@Override
|
||||
public void handleIntent(Intent intent) {
|
||||
|
@ -421,6 +420,13 @@ public final class PopupVideoPlayer extends Service {
|
|||
public void initViews(View rootView) {
|
||||
super.initViews(rootView);
|
||||
resizingIndicator = rootView.findViewById(R.id.resizing_indicator);
|
||||
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
|
||||
fullScreenButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onFullScreenButtonClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -465,7 +471,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
} else {
|
||||
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().url)
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().getUrl())
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
|
||||
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
@ -577,6 +583,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
super.sync(item, info);
|
||||
updateMetadata();
|
||||
}
|
||||
|
@ -617,7 +624,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
onVideoPlayPause();
|
||||
break;
|
||||
case ACTION_OPEN_CONTROLS:
|
||||
openControl(getApplicationContext());
|
||||
NavigationHelper.openPopupPlayerControl(getApplicationContext());
|
||||
break;
|
||||
case ACTION_REPEAT:
|
||||
onRepeatClicked();
|
||||
|
@ -791,11 +798,20 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
|
||||
if (playerImpl == null) return false;
|
||||
if (Math.abs(velocityX) > SHUTDOWN_FLING_VELOCITY) {
|
||||
if (DEBUG) Log.d(TAG, "Popup close fling velocity= " + velocityX);
|
||||
|
||||
final float absVelocityX = Math.abs(velocityX);
|
||||
final float absVelocityY = Math.abs(velocityY);
|
||||
if (absVelocityX > shutdownFlingVelocity) {
|
||||
onClose();
|
||||
return true;
|
||||
} else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||
if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
|
||||
if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
|
||||
checkPositionBounds();
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -835,6 +851,8 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
savePositionAndSize();
|
||||
}
|
||||
|
||||
v.performClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -873,23 +891,25 @@ public final class PopupVideoPlayer extends Service {
|
|||
private final Context context;
|
||||
private final Handler mainHandler;
|
||||
|
||||
FetcherHandler(Context context, int serviceId, String url) {
|
||||
private FetcherHandler(Context context, int serviceId, String url) {
|
||||
this.mainHandler = new Handler(PopupVideoPlayer.this.getMainLooper());
|
||||
this.context = context;
|
||||
this.url = url;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
/*package-private*/ void onReceive(final StreamInfo info) {
|
||||
private void onReceive(final StreamInfo info) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerImpl.initPlayback(new SinglePlayQueue(info));
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(getApplicationContext(),
|
||||
PopupVideoPlayer.class, new SinglePlayQueue(info));
|
||||
playerImpl.handleIntent(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void onError(final Throwable exception) {
|
||||
private void onError(final Throwable exception) {
|
||||
if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]");
|
||||
exception.printStackTrace();
|
||||
mainHandler.post(new Runnable() {
|
||||
|
@ -915,7 +935,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
stopSelf();
|
||||
}
|
||||
|
||||
/*package-private*/ void onReCaptchaException() {
|
||||
private void onReCaptchaException() {
|
||||
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||
// Starting ReCaptcha Challenge Activity
|
||||
Intent intent = new Intent(context, ReCaptchaActivity.class);
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.Player;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
|
@ -57,6 +58,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61;
|
||||
private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97;
|
||||
|
||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||
|
||||
private View rootView;
|
||||
|
||||
private RecyclerView itemsList;
|
||||
|
@ -225,6 +228,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
itemsList.setAdapter(player.getPlayQueueAdapter());
|
||||
itemsList.setClickable(true);
|
||||
itemsList.setLongClickable(true);
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(getQueueScrollListener());
|
||||
|
||||
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(itemsList);
|
||||
|
@ -286,6 +291,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
player.setPlaybackSpeed(playbackSpeed);
|
||||
return true;
|
||||
}
|
||||
|
@ -304,6 +311,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
player.setPlaybackPitch(playbackPitch);
|
||||
return true;
|
||||
}
|
||||
|
@ -317,6 +326,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
return true;
|
||||
|
@ -339,6 +350,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
// Component Helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) {
|
||||
player.getPlayQueue().fetch();
|
||||
} else if (itemsList != null) {
|
||||
itemsList.clearOnScrollListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
|
||||
@Override
|
||||
|
@ -349,7 +373,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
final int sourceIndex = source.getLayoutPosition();
|
||||
final int targetIndex = target.getLayoutPosition();
|
||||
player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -372,11 +396,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return new PlayQueueItemBuilder.OnSelectedListener() {
|
||||
@Override
|
||||
public void selected(PlayQueueItem item, View view) {
|
||||
player.onSelected(item);
|
||||
if (player != null) player.onSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlayQueueItem item, View view) {
|
||||
if (player == null) return;
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) buildItemPopupMenu(item, view);
|
||||
}
|
||||
|
@ -393,7 +419,23 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void scrollToSelected() {
|
||||
itemsList.smoothScrollToPosition(player.getPlayQueue().getIndex());
|
||||
if (player == null) return;
|
||||
|
||||
final int currentPlayingIndex = player.getPlayQueue().getIndex();
|
||||
final int currentVisibleIndex;
|
||||
if (itemsList.getLayoutManager() instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager layout = ((LinearLayoutManager) itemsList.getLayoutManager());
|
||||
currentVisibleIndex = layout.findFirstVisibleItemPosition();
|
||||
} else {
|
||||
currentVisibleIndex = 0;
|
||||
}
|
||||
|
||||
final int distance = Math.abs(currentPlayingIndex - currentVisibleIndex);
|
||||
if (distance < SMOOTH_SCROLL_MAXIMUM_DISTANCE) {
|
||||
itemsList.smoothScrollToPosition(currentPlayingIndex);
|
||||
} else {
|
||||
itemsList.scrollToPosition(currentPlayingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -402,6 +444,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (player == null) return;
|
||||
|
||||
if (view.getId() == repeatButton.getId()) {
|
||||
player.onRepeatClicked();
|
||||
|
||||
|
@ -450,7 +494,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
player.simpleExoPlayer.seekTo(seekBar.getProgress());
|
||||
if (player != null) player.simpleExoPlayer.seekTo(seekBar.getProgress());
|
||||
seekDisplay.setVisibility(View.GONE);
|
||||
seeking = false;
|
||||
}
|
||||
|
@ -485,7 +529,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onMetadataUpdate(StreamInfo info) {
|
||||
if (info != null) {
|
||||
metadataTitle.setText(info.name);
|
||||
metadataTitle.setText(info.getName());
|
||||
metadataArtist.setText(info.uploader_name);
|
||||
scrollToSelected();
|
||||
}
|
||||
|
|
|
@ -75,7 +75,13 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|||
* @author mauriciocolli
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, Player.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
|
||||
public abstract class VideoPlayer extends BasePlayer
|
||||
implements SimpleExoPlayer.VideoListener,
|
||||
SeekBar.OnSeekBarChangeListener,
|
||||
View.OnClickListener,
|
||||
Player.EventListener,
|
||||
PopupMenu.OnMenuItemClickListener,
|
||||
PopupMenu.OnDismissListener {
|
||||
public static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
public final String TAG;
|
||||
|
||||
|
@ -124,12 +130,11 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
|
||||
private View topControlsRoot;
|
||||
private TextView qualityTextView;
|
||||
private ImageButton fullScreenButton;
|
||||
|
||||
private ValueAnimator controlViewAnimator;
|
||||
private Handler controlsVisibilityHandler = new Handler();
|
||||
|
||||
private boolean isSomePopupMenuVisible = false;
|
||||
boolean isSomePopupMenuVisible = false;
|
||||
private int qualityPopupMenuGroupId = 69;
|
||||
private PopupMenu qualityPopupMenu;
|
||||
|
||||
|
@ -166,7 +171,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
||||
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
||||
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
|
||||
this.fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
|
||||
|
||||
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
|
||||
|
||||
|
@ -186,7 +190,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
super.initListeners();
|
||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||
playbackSpeedTextView.setOnClickListener(this);
|
||||
fullScreenButton.setOnClickListener(this);
|
||||
qualityTextView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
|
@ -272,24 +275,25 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
|
||||
final VideoStream video;
|
||||
final int index;
|
||||
if (playbackQuality == null) {
|
||||
final int index = getDefaultResolutionIndex(videos);
|
||||
video = videos.get(index);
|
||||
index = getDefaultResolutionIndex(videos);
|
||||
} else {
|
||||
final int index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
video = videos.get(index);
|
||||
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
}
|
||||
if (index < 0 || index >= videos.size()) return null;
|
||||
final VideoStream video = videos.get(index);
|
||||
|
||||
final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
|
||||
final MediaSource streamSource = buildMediaSource(video.getUrl(), MediaFormat.getSuffixById(video.format));
|
||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||
if (!video.isVideoOnly || audio == null) return streamSource;
|
||||
|
||||
// Merge with audio stream in case if video does not contain audio
|
||||
final MediaSource audioSource = buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
final MediaSource audioSource = buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.format));
|
||||
return new MergingMediaSource(streamSource, audioSource);
|
||||
}
|
||||
|
||||
|
@ -453,9 +457,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
if (v.getId() == fullScreenButton.getId()) {
|
||||
onFullScreenButtonClicked();
|
||||
} else if (v.getId() == qualityTextView.getId()) {
|
||||
if (v.getId() == qualityTextView.getId()) {
|
||||
onQualitySelectorClicked();
|
||||
} else if (v.getId() == playbackSpeedTextView.getId()) {
|
||||
onPlaybackSpeedClicked();
|
||||
|
@ -753,14 +755,14 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
return qualityTextView;
|
||||
}
|
||||
|
||||
public ImageButton getFullScreenButton() {
|
||||
return fullScreenButton;
|
||||
}
|
||||
|
||||
public PopupMenu getQualityPopupMenu() {
|
||||
return qualityPopupMenu;
|
||||
}
|
||||
|
||||
public PopupMenu getPlaybackSpeedPopupMenu() {
|
||||
return playbackSpeedPopupMenu;
|
||||
}
|
||||
|
||||
public View getSurfaceForeground() {
|
||||
return surfaceForeground;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import java.text.NumberFormat;
|
|||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class PlayerHelper {
|
||||
private PlayerHelper() {}
|
||||
|
||||
|
@ -56,6 +58,10 @@ public class PlayerHelper {
|
|||
return isUsingOldPlayer(context, false);
|
||||
}
|
||||
|
||||
public static boolean isRememberingPopupDimensions(@Nonnull final Context context) {
|
||||
return isRememberingPopupDimensions(context, true);
|
||||
}
|
||||
|
||||
public static long getPreferredCacheSize(@NonNull final Context context) {
|
||||
return 64 * 1024 * 1024L;
|
||||
}
|
||||
|
@ -83,6 +89,15 @@ public class PlayerHelper {
|
|||
public static boolean isUsingDSP(@NonNull final Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getShutdownFlingVelocity(@Nonnull final Context context) {
|
||||
return 10000;
|
||||
}
|
||||
|
||||
public static int getTossFlingVelocity(@Nonnull final Context context) {
|
||||
return 2500;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Private helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -103,4 +118,8 @@ public class PlayerHelper {
|
|||
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
|
||||
}
|
||||
|
||||
private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import io.reactivex.subjects.PublishSubject;
|
|||
public class MediaSourceManager {
|
||||
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
|
||||
// One-side rolling window size for default loading
|
||||
// Effectively loads windowSize * 2 + 1 streams, must be greater than 0
|
||||
// Effectively loads windowSize * 2 + 1 streams per call to load, must be greater than 0
|
||||
private final int windowSize;
|
||||
private final PlaybackListener playbackListener;
|
||||
private final PlayQueue playQueue;
|
||||
|
@ -38,7 +38,7 @@ public class MediaSourceManager {
|
|||
// The higher it is, the less loading occurs during rapid noncritical timeline changes
|
||||
// Not recommended to go below 100ms
|
||||
private final long loadDebounceMillis;
|
||||
private final PublishSubject<Long> loadSignal;
|
||||
private final PublishSubject<Long> debouncedLoadSignal;
|
||||
private final Disposable debouncedLoader;
|
||||
|
||||
private final DeferredMediaSource.Callback sourceBuilder;
|
||||
|
@ -52,7 +52,7 @@ public class MediaSourceManager {
|
|||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, 1, 1000L);
|
||||
this(listener, playQueue, 1, 400L);
|
||||
}
|
||||
|
||||
private MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
|
@ -69,7 +69,7 @@ public class MediaSourceManager {
|
|||
this.loadDebounceMillis = loadDebounceMillis;
|
||||
|
||||
this.syncReactor = new SerialDisposable();
|
||||
this.loadSignal = PublishSubject.create();
|
||||
this.debouncedLoadSignal = PublishSubject.create();
|
||||
this.debouncedLoader = getDebouncedLoader();
|
||||
|
||||
this.sourceBuilder = getSourceBuilder();
|
||||
|
@ -101,7 +101,7 @@ public class MediaSourceManager {
|
|||
* Dispose the manager and releases all message buses and loaders.
|
||||
* */
|
||||
public void dispose() {
|
||||
if (loadSignal != null) loadSignal.onComplete();
|
||||
if (debouncedLoadSignal != null) debouncedLoadSignal.onComplete();
|
||||
if (debouncedLoader != null) debouncedLoader.dispose();
|
||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||
if (syncReactor != null) syncReactor.dispose();
|
||||
|
@ -118,7 +118,7 @@ public class MediaSourceManager {
|
|||
* Unblocks the player once the item at the current index is loaded.
|
||||
* */
|
||||
public void load() {
|
||||
loadSignal.onNext(System.currentTimeMillis());
|
||||
loadDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,12 +157,12 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty()) {
|
||||
if (playQueue.isEmpty() && playQueue.isComplete()) {
|
||||
playbackListener.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// why no pattern matching in Java =(
|
||||
// Event specific action
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
case REORDER:
|
||||
|
@ -172,37 +172,34 @@ public class MediaSourceManager {
|
|||
case APPEND:
|
||||
populateSources();
|
||||
break;
|
||||
case SELECT:
|
||||
sync();
|
||||
break;
|
||||
case REMOVE:
|
||||
final RemoveEvent removeEvent = (RemoveEvent) event;
|
||||
remove(removeEvent.getRemoveIndex());
|
||||
// Sync only when the currently playing is removed
|
||||
if (removeEvent.getQueueIndex() == removeEvent.getRemoveIndex()) sync();
|
||||
break;
|
||||
case MOVE:
|
||||
final MoveEvent moveEvent = (MoveEvent) event;
|
||||
move(moveEvent.getFromIndex(), moveEvent.getToIndex());
|
||||
break;
|
||||
case SELECT:
|
||||
case RECOVERY:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Loading and Syncing
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
case REORDER:
|
||||
case ERROR:
|
||||
case APPEND:
|
||||
loadInternal(); // low frequency, critical events
|
||||
loadImmediate(); // low frequency, critical events
|
||||
break;
|
||||
case APPEND:
|
||||
case REMOVE:
|
||||
case SELECT:
|
||||
case MOVE:
|
||||
case RECOVERY:
|
||||
default:
|
||||
load(); // high frequency or noncritical events
|
||||
loadDebounced(); // high frequency or noncritical events
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -262,7 +259,11 @@ public class MediaSourceManager {
|
|||
syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError));
|
||||
}
|
||||
|
||||
private void loadInternal() {
|
||||
private void loadDebounced() {
|
||||
debouncedLoadSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void loadImmediate() {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
|
@ -290,7 +291,9 @@ public class MediaSourceManager {
|
|||
|
||||
final DeferredMediaSource mediaSource = (DeferredMediaSource) sources.getMediaSource(playQueue.indexOf(item));
|
||||
if (mediaSource.state() == DeferredMediaSource.STATE_PREPARED) mediaSource.load();
|
||||
if (tryUnblock()) sync();
|
||||
|
||||
tryUnblock();
|
||||
if (!isBlocked) sync();
|
||||
}
|
||||
|
||||
private void resetSources() {
|
||||
|
@ -307,13 +310,13 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private Disposable getDebouncedLoader() {
|
||||
return loadSignal
|
||||
return debouncedLoadSignal
|
||||
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Long>() {
|
||||
@Override
|
||||
public void accept(Long timestamp) throws Exception {
|
||||
loadInternal();
|
||||
loadImmediate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ public interface PlaybackListener {
|
|||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
@Nullable
|
||||
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
boolean isInitial;
|
||||
boolean isComplete;
|
||||
|
||||
int serviceId;
|
||||
String baseUrl;
|
||||
String nextUrl;
|
||||
|
||||
transient Disposable fetchReactor;
|
||||
|
||||
AbstractInfoPlayQueue(final U item) {
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.<InfoItem>emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
}
|
||||
|
||||
abstract protected String getTag();
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
SingleObserver<T> getHeadListObserver() {
|
||||
return new SingleObserver<T>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull T result) {
|
||||
isInitial = false;
|
||||
if (!result.has_more_streams) isComplete = true;
|
||||
nextUrl = result.next_streams_url;
|
||||
|
||||
append(extractListItems(result.related_streams));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SingleObserver<ListExtractor.NextItemsResult> getNextItemsObserver() {
|
||||
return new SingleObserver<ListExtractor.NextItemsResult>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
|
||||
if (!result.hasMoreStreams()) isComplete = true;
|
||||
nextUrl = result.nextItemsUrl;
|
||||
|
||||
append(extractListItems(result.nextItemsList));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<InfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
|
||||
public ChannelPlayQueue(final ChannelInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "ChannelPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextItemsObserver());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ExternalPlayQueue extends PlayQueue {
|
||||
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
private boolean isComplete;
|
||||
|
||||
private int serviceId;
|
||||
private String baseUrl;
|
||||
private String nextUrl;
|
||||
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
public ExternalPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(index, extractPlaylistItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistObserver());
|
||||
}
|
||||
|
||||
private SingleObserver<ListExtractor.NextItemsResult> getPlaylistObserver() {
|
||||
return new SingleObserver<ListExtractor.NextItemsResult>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
|
||||
if (!result.hasMoreStreams()) isComplete = true;
|
||||
nextUrl = result.nextItemsUrl;
|
||||
|
||||
append(extractPlaylistItems(result.nextItemsList));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractPlaylistItems(final List<InfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -123,7 +123,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
* May throw {@link IndexOutOfBoundsException}.
|
||||
* */
|
||||
public PlayQueueItem getItem(int index) {
|
||||
if (index >= streams.size() || streams.get(index) == null) return null;
|
||||
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
|
||||
return streams.get(index);
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
queueIndex.set(currentIndex % (size - 1));
|
||||
|
||||
} else if (currentIndex == removeIndex && currentIndex == size - 1){
|
||||
queueIndex.set(removeIndex - 1);
|
||||
queueIndex.set(0);
|
||||
}
|
||||
|
||||
if (backup != null) {
|
||||
|
|
|
@ -30,12 +30,12 @@ public class PlayQueueItem implements Serializable {
|
|||
private transient Single<StreamInfo> stream;
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info) {
|
||||
this(info.name, info.url, info.service_id, info.duration, info.thumbnail_url, info.uploader_name);
|
||||
this(info.getName(), info.getUrl(), info.getServiceId(), info.duration, info.thumbnail_url, info.uploader_name);
|
||||
this.stream = Single.just(info);
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
|
||||
this(item.getName(), item.getUrl(), item.getServiceId(), item.duration, item.thumbnail_url, item.uploader_name);
|
||||
}
|
||||
|
||||
private PlayQueueItem(final String name, final String url, final int serviceId,
|
||||
|
|
|
@ -107,6 +107,8 @@ public class PlayQueueItemBuilder {
|
|||
.bitmapConfig(Bitmap.Config.RGB_565) // Users won't be able to see much anyways
|
||||
.preProcessor(bitmapProcessor)
|
||||
.imageScaleType(ImageScaleType.EXACTLY)
|
||||
.cacheInMemory(true)
|
||||
.cacheOnDisk(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> {
|
||||
public PlaylistPlayQueue(final PlaylistInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextItemsObserver());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public final class SinglePlayQueue extends PlayQueue {
|
||||
public SinglePlayQueue(final StreamInfoItem item) {
|
||||
this(new PlayQueueItem(item));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
this(new PlayQueueItem(info));
|
||||
}
|
||||
|
||||
private SinglePlayQueue(final PlayQueueItem playQueueItem) {
|
||||
super(0, Collections.singletonList(playQueueItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -160,7 +160,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
key = k;
|
||||
}
|
||||
}
|
||||
String[] el = new String[]{report.get(key)};
|
||||
String[] el = new String[]{report.get(key).toString()};
|
||||
|
||||
Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
|
|
|
@ -26,6 +26,9 @@ import android.util.Log;
|
|||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
public final class InfoCache {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
@ -37,9 +40,9 @@ public final class InfoCache {
|
|||
* Trim the cache to this size
|
||||
*/
|
||||
private static final int TRIM_CACHE_TO = 30;
|
||||
private static final int DEFAULT_TIMEOUT_HOURS = 4;
|
||||
|
||||
// TODO: Replace to one with timeout (like the one from guava)
|
||||
private static final LruCache<String, Info> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
private static final LruCache<String, CacheData> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
|
||||
private InfoCache() {
|
||||
//no instance
|
||||
|
@ -52,28 +55,29 @@ public final class InfoCache {
|
|||
public Info getFromKey(int serviceId, @NonNull String url) {
|
||||
if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
return lruCache.get(serviceId + url);
|
||||
return getInfo(lruCache, keyOf(serviceId, url));
|
||||
}
|
||||
}
|
||||
|
||||
public void putInfo(@NonNull Info info) {
|
||||
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.put(info.service_id + info.url, info);
|
||||
final CacheData data = new CacheData(info, DEFAULT_TIMEOUT_HOURS, TimeUnit.HOURS);
|
||||
lruCache.put(keyOf(info), data);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(@NonNull Info info) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: info = [" + info + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(info.service_id + info.url);
|
||||
lruCache.remove(keyOf(info));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(int serviceId, @NonNull String url) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(serviceId + url);
|
||||
lruCache.remove(keyOf(serviceId, url));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +91,7 @@ public final class InfoCache {
|
|||
public void trimCache() {
|
||||
if (DEBUG) Log.d(TAG, "trimCache() called");
|
||||
synchronized (lruCache) {
|
||||
removeStaleCache(lruCache);
|
||||
lruCache.trimToSize(TRIM_CACHE_TO);
|
||||
}
|
||||
}
|
||||
|
@ -97,4 +102,51 @@ public final class InfoCache {
|
|||
}
|
||||
}
|
||||
|
||||
private static String keyOf(@NonNull final Info info) {
|
||||
return keyOf(info.getServiceId(), info.getUrl());
|
||||
}
|
||||
|
||||
private static String keyOf(final int serviceId, @NonNull final String url) {
|
||||
return serviceId + url;
|
||||
}
|
||||
|
||||
private static void removeStaleCache(@NonNull final LruCache<String, CacheData> cache) {
|
||||
for (Map.Entry<String, CacheData> entry : cache.snapshot().entrySet()) {
|
||||
final CacheData data = entry.getValue();
|
||||
if (data != null && data.isExpired()) {
|
||||
cache.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Info getInfo(@NonNull final LruCache<String, CacheData> cache,
|
||||
@NonNull final String key) {
|
||||
final CacheData data = cache.get(key);
|
||||
if (data == null) return null;
|
||||
|
||||
if (data.isExpired()) {
|
||||
cache.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.info;
|
||||
}
|
||||
|
||||
final private static class CacheData {
|
||||
final private long expireTimestamp;
|
||||
final private Info info;
|
||||
|
||||
private CacheData(@NonNull final Info info,
|
||||
final long timeout,
|
||||
@NonNull final TimeUnit timeUnit) {
|
||||
this.expireTimestamp = System.currentTimeMillis() +
|
||||
TimeUnit.MILLISECONDS.convert(timeout, timeUnit);
|
||||
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
private boolean isExpired() {
|
||||
return System.currentTimeMillis() > expireTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ public final class ListHelper {
|
|||
int highestQualityIndex = 0;
|
||||
if (audioStreams.size() > 1) for (int i = 1; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.average_bitrate >= audioStreams.get(highestQualityIndex).average_bitrate) highestQualityIndex = i;
|
||||
if (audioStream.getAverageBitrate() >= audioStreams.get(highestQualityIndex).getAverageBitrate()) highestQualityIndex = i;
|
||||
}
|
||||
return highestQualityIndex;
|
||||
}
|
||||
|
@ -124,10 +124,10 @@ public final class ListHelper {
|
|||
int highestQualityIndex = -1;
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (highestQualityIndex == -1 && audioStream.format == defaultFormat.id) highestQualityIndex = i;
|
||||
if (highestQualityIndex == -1 && audioStream.getFormat() == defaultFormat) highestQualityIndex = i;
|
||||
|
||||
if (highestQualityIndex != -1 && audioStream.format == defaultFormat.id
|
||||
&& audioStream.average_bitrate > audioStreams.get(highestQualityIndex).average_bitrate) {
|
||||
if (highestQualityIndex != -1 && audioStream.getFormat() == defaultFormat
|
||||
&& audioStream.getAverageBitrate() > audioStreams.get(highestQualityIndex).getAverageBitrate()) {
|
||||
highestQualityIndex = i;
|
||||
}
|
||||
}
|
||||
|
@ -171,23 +171,23 @@ public final class ListHelper {
|
|||
|
||||
if (videoOnlyStreams != null) {
|
||||
for (VideoStream stream : videoOnlyStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
if (videoStreams != null) {
|
||||
for (VideoStream stream : videoStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all to the hashmap
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream);
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.getResolution(), videoStream);
|
||||
|
||||
// Override the values when the key == resolution, with the defaultFormat
|
||||
for (VideoStream videoStream : retList) {
|
||||
if (videoStream.format == defaultFormat.id) hashMap.put(videoStream.resolution, videoStream);
|
||||
if (videoStream.getFormat() == defaultFormat) hashMap.put(videoStream.getResolution(), videoStream);
|
||||
}
|
||||
|
||||
retList.clear();
|
||||
|
@ -219,8 +219,8 @@ public final class ListHelper {
|
|||
Collections.sort(videoStreams, new Comparator<VideoStream>() {
|
||||
@Override
|
||||
public int compare(VideoStream o1, VideoStream o2) {
|
||||
int res1 = Integer.parseInt(o1.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
int res2 = Integer.parseInt(o2.resolution.replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
int res1 = Integer.parseInt(o1.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
int res2 = Integer.parseInt(o2.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", ""));
|
||||
|
||||
return ascendingOrder ? res1 - res2 : res2 - res1;
|
||||
}
|
||||
|
@ -235,9 +235,9 @@ public final class ListHelper {
|
|||
int defaultStreamIndex = -1;
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream stream = videoStreams.get(i);
|
||||
if (defaultStreamIndex == -1 && stream.resolution.equals(defaultResolution)) defaultStreamIndex = i;
|
||||
if (defaultStreamIndex == -1 && stream.getResolution().equals(defaultResolution)) defaultStreamIndex = i;
|
||||
|
||||
if (stream.format == defaultFormat.id && stream.resolution.equals(defaultResolution)) {
|
||||
if (stream.getFormat() == defaultFormat && stream.getResolution().equals(defaultResolution)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import android.content.ActivityNotFoundException;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
|
@ -19,7 +20,9 @@ import org.schabi.newpipe.download.DownloadActivity;
|
|||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
|
@ -28,7 +31,12 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
|||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.history.HistoryActivity;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.BackgroundPlayerActivity;
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayerActivity;
|
||||
import org.schabi.newpipe.player.VideoPlayer;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
@ -77,6 +85,29 @@ public class NavigationHelper {
|
|||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
|
||||
}
|
||||
|
||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue) {
|
||||
context.startActivity(getPlayerIntent(context, MainVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void playOnPopupPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerIntent(context, PopupVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue));
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Through FragmentManager
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -230,6 +261,23 @@ public class NavigationHelper {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static void openBackgroundPlayerControl(final Context context) {
|
||||
openServicePlayerControl(context, BackgroundPlayerActivity.class);
|
||||
}
|
||||
|
||||
public static void openPopupPlayerControl(final Context context) {
|
||||
openServicePlayerControl(context, PopupVideoPlayerActivity.class);
|
||||
}
|
||||
|
||||
private static void openServicePlayerControl(final Context context, final Class clazz) {
|
||||
final Intent intent = new Intent(context, clazz);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Link handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -6,8 +6,8 @@ import android.app.PendingIntent;
|
|||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
|
@ -15,7 +15,6 @@ import android.os.HandlerThread;
|
|||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.NotificationCompat.Builder;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.PermissionChecker;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
@ -96,12 +95,12 @@ public class DownloadManagerService extends Service {
|
|||
openDownloadListIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
Drawable icon = ContextCompat.getDrawable(this, R.mipmap.ic_launcher);
|
||||
Bitmap iconBitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
|
||||
|
||||
Builder builder = new Builder(this, getString(R.string.notification_channel_id))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
|
||||
.setLargeIcon(iconBitmap)
|
||||
.setContentTitle(getString(R.string.msg_running))
|
||||
.setContentText(getString(R.string.msg_running_detail));
|
||||
|
||||
|
|
BIN
app/src/main/res/drawable-hdpi/ic_arrow_top_left_black_24dp.png
Normal file
After Width: | Height: | Size: 554 B |
BIN
app/src/main/res/drawable-hdpi/ic_arrow_top_left_white_24dp.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png
Normal file
After Width: | Height: | Size: 132 B |
BIN
app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png
Normal file
After Width: | Height: | Size: 134 B |
BIN
app/src/main/res/drawable-hdpi/ic_newpipe_triangle_white.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_top_left_black_24dp.png
Normal file
After Width: | Height: | Size: 418 B |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_top_left_white_24dp.png
Normal file
After Width: | Height: | Size: 427 B |
BIN
app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png
Normal file
After Width: | Height: | Size: 108 B |
BIN
app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png
Normal file
After Width: | Height: | Size: 112 B |
BIN
app/src/main/res/drawable-mdpi/ic_newpipe_triangle_white.png
Normal file
After Width: | Height: | Size: 328 B |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_top_left_black_24dp.png
Normal file
After Width: | Height: | Size: 492 B |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_top_left_white_24dp.png
Normal file
After Width: | Height: | Size: 500 B |
BIN
app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png
Normal file
After Width: | Height: | Size: 158 B |
BIN
app/src/main/res/drawable-xhdpi/ic_newpipe_triangle_white.png
Normal file
After Width: | Height: | Size: 628 B |
After Width: | Height: | Size: 570 B |
After Width: | Height: | Size: 573 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png
Normal file
After Width: | Height: | Size: 205 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png
Normal file
After Width: | Height: | Size: 216 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_newpipe_triangle_white.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 675 B |
After Width: | Height: | Size: 730 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png
Normal file
After Width: | Height: | Size: 305 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_newpipe_triangle_white.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -209,7 +209,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/screenRotationButton"
|
||||
android:layout_toLeftOf="@+id/queueButton"
|
||||
android:gravity="center"
|
||||
android:minHeight="35dp"
|
||||
android:minWidth="40dp"
|
||||
|
@ -218,28 +218,13 @@
|
|||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="1x" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/screenRotationButton"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/queueButton"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_screen_rotation_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/queueButton"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/fullScreenButton"
|
||||
android:layout_toLeftOf="@+id/moreOptionsButton"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
@ -249,16 +234,17 @@
|
|||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/fullScreenButton"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:id="@+id/moreOptionsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:padding="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_fullscreen_exit_white"
|
||||
android:src="?attr/options"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -454,4 +440,4 @@
|
|||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -6,75 +6,90 @@
|
|||
android:id="@+id/channel_header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp">
|
||||
android:background="?attr/contrast_background_color">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/channel_banner_image"
|
||||
<RelativeLayout
|
||||
android:id="@+id/channel_metadata"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/channel_banner"
|
||||
tools:ignore="ContentDescription"/>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/channel_avatar_view"
|
||||
android:layout_width="@dimen/channel_avatar_size"
|
||||
android:layout_height="@dimen/channel_avatar_size"
|
||||
android:layout_alignTop="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:src="@drawable/buddy"
|
||||
app:civ_border_color="#ffffff"
|
||||
app:civ_border_width="2dp"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
<ImageView
|
||||
android:id="@+id/channel_banner_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/channel_banner"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_title_view"
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/channel_avatar_view"
|
||||
android:layout_width="@dimen/channel_avatar_size"
|
||||
android:layout_height="@dimen/channel_avatar_size"
|
||||
android:layout_alignTop="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:src="@drawable/buddy"
|
||||
app:civ_border_color="#ffffff"
|
||||
app:civ_border_width="2dp"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_toLeftOf="@+id/channel_subscribe_button"
|
||||
android:layout_toRightOf="@+id/channel_avatar_view"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_title_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_subscriber_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/channel_title_view"
|
||||
android:layout_alignRight="@+id/channel_title_view"
|
||||
android:layout_below="@+id/channel_title_view"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left|center"
|
||||
android:lines="1"
|
||||
android:textSize="@dimen/channel_subscribers_text_size"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="123,141,411 subscribers"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatButton
|
||||
android:id="@+id/channel_subscribe_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/channel_banner_image"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:text="@string/subscribe_button_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:theme="@style/RedButton"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_toLeftOf="@+id/channel_subscribe_button"
|
||||
android:layout_toRightOf="@+id/channel_avatar_view"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_title_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor"/>
|
||||
android:layout_below="@id/channel_metadata">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_subscriber_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/channel_title_view"
|
||||
android:layout_alignRight="@+id/channel_title_view"
|
||||
android:layout_below="@+id/channel_title_view"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left|center"
|
||||
android:lines="1"
|
||||
android:textSize="@dimen/channel_subscribers_text_size"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="123,141,411 subscribers"
|
||||
tools:visibility="visible"/>
|
||||
<include layout="@layout/playlist_control" />
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v7.widget.AppCompatButton
|
||||
android:id="@+id/channel_subscribe_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/channel_banner_image"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:text="@string/subscribe_button_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:theme="@style/RedButton"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
40
app/src/main/res/layout/dialog_title.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:paddingLeft="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding"
|
||||
android:paddingTop="@dimen/video_item_search_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/channel_item_detail_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. "/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemTitleView"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
tools:text="TYPE" />
|
||||
</RelativeLayout>
|
|
@ -45,6 +45,7 @@
|
|||
android:id="@+id/app_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/app_description" />
|
||||
|
||||
<TextView
|
||||
|
@ -60,7 +61,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/contribution_encouragement" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/github_link"
|
||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
||||
|
@ -70,26 +70,46 @@
|
|||
android:text="@string/view_on_github" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_license_title"
|
||||
android:id="@+id/title_donate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/app_license_title"
|
||||
android:text="@string/donation_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_license"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_license" />
|
||||
android:text="@string/donation_encouragement" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/app_read_license"
|
||||
android:id="@+id/donation_link"
|
||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/read_full_license" />
|
||||
android:text="@string/give_back" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_website"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/website_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/website_encouragement" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/website_link"
|
||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/open_in_browser" />
|
||||
|
||||
</LinearLayout>
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
|
|
@ -11,12 +11,40 @@
|
|||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_license_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_vertical_margin"
|
||||
android:text="@string/app_license_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_license"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/app_license" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/app_read_license"
|
||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/activity_vertical_margin"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/read_full_license" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/title_licenses"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
|
|
|
@ -1,36 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_suggestion_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="?attr/history"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_suggestion_query"
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestion_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Search query"/>
|
||||
</LinearLayout>
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toLeftOf="@id/suggestion_insert"
|
||||
android:layout_toStartOf="@id/suggestion_insert"
|
||||
android:layout_centerVertical="true"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_suggestion_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="?attr/history"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_suggestion_query"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Search query"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestion_insert"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="10dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="?attr/search_add"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
|
@ -28,11 +29,12 @@
|
|||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Channel Title, Lorem ipsum"/>
|
||||
tools:text="Channel Title, Lorem ipsum" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemChannelDescriptionView"
|
||||
|
@ -41,11 +43,12 @@
|
|||
android:layout_above="@+id/itemAdditionalDetails"
|
||||
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
|
@ -53,6 +56,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
@ -48,6 +49,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
|
@ -60,6 +62,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemVideoTitleView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
|
@ -71,6 +74,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
@ -48,11 +49,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemUploaderView"
|
||||
|
@ -60,8 +63,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemVideoTitleView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="Uploader"/>
|
||||
tools:text="Uploader" />
|
||||
</RelativeLayout>
|
|
@ -79,8 +79,7 @@
|
|||
android:layout_toLeftOf="@id/itemHandle"
|
||||
android:layout_toStartOf="@id/itemHandle"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. "/>
|
||||
|
@ -94,7 +93,8 @@
|
|||
android:layout_toEndOf="@id/itemThumbnailView"
|
||||
android:layout_toLeftOf="@id/itemHandle"
|
||||
android:layout_toStartOf="@id/itemHandle"
|
||||
android:lines="1"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
tools:text="Uploader"/>
|
||||
|
|
|
@ -34,22 +34,22 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
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"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
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>
|
||||
|
||||
|
|
|
@ -46,22 +46,22 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
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"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/background_subtext_color"
|
||||
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -80,7 +80,6 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/notificationTime"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
|
@ -92,6 +91,7 @@
|
|||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/background_subtext_color"
|
||||
tools:text="Duis posuere"/>
|
||||
|
||||
<RelativeLayout
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
android:layout_alignParentRight="true"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_fullscreen_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
|
|
@ -29,22 +29,22 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
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"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
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>
|
||||
|
|
83
app/src/main/res/layout/playlist_control.xml
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/playlist_ctrl_height"
|
||||
android:id="@+id/playlist_control"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:visibility="invisible">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playlist_ctrl_play_bg_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_background_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/audio"
|
||||
android:drawableStart="?attr/audio"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/anchorLeft"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:layout_marginBottom="@dimen/playlist_ctrl_seperator_margin"
|
||||
android:layout_marginTop="@dimen/playlist_ctrl_seperator_margin"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/playlist_ctrl_play_all_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/play_all"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/anchorRight"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:layout_marginBottom="@dimen/playlist_ctrl_seperator_margin"
|
||||
android:layout_marginTop="@dimen/playlist_ctrl_seperator_margin"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/playlist_ctrl_play_popup_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_popup_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/popup"
|
||||
android:drawableStart="?attr/popup"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -6,8 +6,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/contrast_background_color"
|
||||
android:paddingBottom="6dp">
|
||||
android:background="?attr/contrast_background_color">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_title_view"
|
||||
|
@ -81,64 +80,14 @@
|
|||
android:textSize="@dimen/playlist_detail_subtext_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="234 videos"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/play_control"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp"
|
||||
android:layout_below="@+id/playlist_meta">
|
||||
android:layout_below="@id/playlist_meta">
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_bg_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/playlist_play_all_button"
|
||||
android:layout_toStartOf="@+id/playlist_play_all_button"
|
||||
android:text="@string/controls_background_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
android:drawableLeft="?attr/audio"
|
||||
android:drawablePadding="4dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_all_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/playlist_play_popup_button"
|
||||
android:layout_toStartOf="@+id/playlist_play_popup_button"
|
||||
android:text="@string/play_all"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_popup_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_alignParentRight="true"
|
||||
android:text="@string/controls_popup_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
android:drawableLeft="?attr/popup"
|
||||
android:drawablePadding="4dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
</RelativeLayout>
|
||||
<include layout="@layout/playlist_control"/>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
21
app/src/main/res/menu/menu_videooptions.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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:icon="@drawable/ic_screen_rotation_white"
|
||||
android:id="@+id/toggleOrientation"
|
||||
android:title="Toggle orientation"
|
||||
app:showAsAction="always|withText" />
|
||||
<item
|
||||
android:icon="@drawable/ic_fullscreen_exit_white"
|
||||
android:id="@+id/switchPopup"
|
||||
android:title="Switch to popup"
|
||||
app:showAsAction="always|withText" />
|
||||
<item android:icon="?audio"
|
||||
android:id="@+id/switchBackground"
|
||||
android:title="Switch to background"
|
||||
app:showAsAction="always|withText" />
|
||||
</menu>
|
||||
|
|
@ -9,16 +9,16 @@
|
|||
<string name="detail_dislikes_img_view_description">عدم الإعجاب</string>
|
||||
<string name="detail_likes_img_view_description">الإعجابات</string>
|
||||
<string name="detail_thumbnail_view_description">صور معاينة الفيديو</string>
|
||||
<string name="detail_uploader_thumbnail_view_description">"Uploader's userpic thumbnail"</string>
|
||||
<string name="did_you_mean">هل تقصد : %1$s ؟</string>
|
||||
<string name="detail_uploader_thumbnail_view_description">الصورة المصغرة الشخصية</string>
|
||||
<string name="did_you_mean">هل تقصد : %1$s ?</string>
|
||||
<string name="download">تنزيل</string>
|
||||
<string name="download_dialog_title">تنزيل</string>
|
||||
<string name="download_path_audio_dialog_title">أدخل مسار التنزيل للملفات الصوتية.</string>
|
||||
<string name="download_path_audio_summary">مسار حفظ التنزيلات الصوتية في.</string>
|
||||
<string name="download_path_audio_dialog_title">أدخل مسار لتنزيل الملفات الصوتية.</string>
|
||||
<string name="download_path_audio_summary">مسار حفظ التنزيلات الصوتية</string>
|
||||
<string name="download_path_audio_title">مسار الصوتيات المحفوظة</string>
|
||||
<string name="download_path_dialog_title">أدخل مسار التنزيل لملفات الفيديو</string>
|
||||
<string name="download_path_summary">مسار حفظ تنزيلات الفيديو في</string>
|
||||
<string name="download_path_title">مسار الفيديوهات المحفوظة</string>
|
||||
<string name="download_path_title">مسار تحميل الفيديو</string>
|
||||
<string name="err_dir_create">"لا يمكن إنشاء مجلد للتنزيلات في '%1$s'"</string>
|
||||
<string name="info_dir_created">"تم إنشاء مجلد تنزيلات في '%1$s'"</string>
|
||||
<string name="install">تثبيت</string>
|
||||
|
@ -33,12 +33,12 @@
|
|||
<string name="play_audio">صوت</string>
|
||||
<string name="play_btn_text">تشغيل</string>
|
||||
<string name="play_with_kodi_title">تشغيل بواسطة Kodi</string>
|
||||
<string name="screen_rotation">تدوير</string>
|
||||
<string name="screen_rotation">التدوير</string>
|
||||
<string name="search">بحث</string>
|
||||
<string name="search_language_title">لغة المحتوى المفضل</string>
|
||||
<string name="settings">الإعدادات</string>
|
||||
<string name="settings_category_appearance_title">المظهر</string>
|
||||
<string name="settings_category_other_title">تعريب JetSub مدونة درويديات</string>
|
||||
<string name="settings_category_other_title">أخرئ</string>
|
||||
<string name="settings_category_video_audio_title">الفيديو والصوتيات</string>
|
||||
<string name="share">مشاركة</string>
|
||||
<string name="share_dialog_title">مشاركة بواسطة</string>
|
||||
|
@ -48,19 +48,19 @@
|
|||
<string name="theme_title">السمة</string>
|
||||
<string name="upload_date_text">تم نشرها في %1$s</string>
|
||||
<string name="url_not_supported_toast">الرابط غير مدعوم</string>
|
||||
<string name="use_external_audio_player_title">استخدام مشغل صوتيات خارجي</string>
|
||||
<string name="use_external_audio_player_title">استخدام مشغل صوت خارجي</string>
|
||||
<string name="use_external_video_player_title">استخدام مشغل فيديو خارجي</string>
|
||||
<string name="use_tor_summary">إجراء التنزيلات من خلال استخدام بروكسي Tor لزيادة الخصوصية ( تشغيل الفيديو المباشر غير مدعوم حتى الأن )</string>
|
||||
<string name="use_tor_summary">(إختبارية) إجراء التنزيلات من خلال استخدام بروكسي Tor لزيادة الخصوصية ( تشغيل الفيديو المباشر غير مدعوم حتى الأن ).</string>
|
||||
<string name="use_tor_title">استخدام Tor</string>
|
||||
<string name="view_count_text">%1$s المشاهدات</string>
|
||||
<string name="webm_description">WebM</string>
|
||||
<string name="blocked_by_gema">Blocked by GEMA.</string>
|
||||
<string name="webm_description">صيغة حرة—WebM</string>
|
||||
<string name="blocked_by_gema">تم حجبه بواسطة GEMA</string>
|
||||
<string name="content_not_available">المحتوى غير متاح.</string>
|
||||
<string name="could_not_load_thumbnails">لم يتمكن من تحميل كل صور المعاينة</string>
|
||||
<string name="general_error">خطأ</string>
|
||||
<string name="parsing_error">لا يمكن تحليل الموقع.</string>
|
||||
<string name="youtube_signature_decryption_error">لا يمكن فك تشفير توقيع رابط الفيديو.</string>
|
||||
<string name="main_bg_subtitle">إضغط على البحث للمواصلة</string>
|
||||
<string name="main_bg_subtitle">انقر على البحث للبدء</string>
|
||||
<string name="subscribe_button_title">إشترك</string>
|
||||
<string name="subscribed_button_title">مشترك</string>
|
||||
<string name="tab_main">الرئيسية</string>
|
||||
|
@ -71,8 +71,8 @@
|
|||
<string name="controls_background_title">الخلفية</string>
|
||||
<string name="autoplay_by_calling_app_title">التشغيل التلقائي</string>
|
||||
<string name="black_theme_title">أسود</string>
|
||||
<string name="enable_watch_history_title">التأريخ</string>
|
||||
<string name="settings_category_history_title">التأريخ</string>
|
||||
<string name="enable_watch_history_title">التاريخ</string>
|
||||
<string name="settings_category_history_title">التاريخ</string>
|
||||
<string name="content">المحتوى</string>
|
||||
<string name="downloads">التنزيلات</string>
|
||||
<string name="downloads_title">التنزيلات</string>
|
||||
|
@ -84,4 +84,182 @@
|
|||
<string name="tab_about">عن التطبيق</string>
|
||||
<string name="title_activity_history">التأريخ</string>
|
||||
<string name="action_history">التأريخ</string>
|
||||
</resources>
|
||||
<string name="open_in_popup_mode">فتح في النافذة المنبثقه</string>
|
||||
<string name="use_external_video_player_summary">بعض خيارات الجودة لن يكون هنالك صوت عند تمكين هذا الخيار</string>
|
||||
<string name="popup_mode_share_menu_title">وضع النافذة NewPipe المنبثقة</string>
|
||||
<string name="channel_unsubscribed">تم إلغاء اشتراك القناة</string>
|
||||
<string name="subscription_change_failed">تعذر تغيير في الاشتراك</string>
|
||||
<string name="subscription_update_failed">تعذر تحديث الاشتراك</string>
|
||||
|
||||
<string name="controls_popup_title">نافذة</string>
|
||||
|
||||
<string name="autoplay_by_calling_app_summary">تشغيل الفيديو تلقائيا عند استدعاء NewPipe من تطبيق آخر</string>
|
||||
<string name="default_popup_resolution_title">الجودة الافتراضية للنوافذ المنبثقة</string>
|
||||
<string name="show_higher_resolutions_title">إظهار جودة أعلى</string>
|
||||
<string name="show_higher_resolutions_summary">فقط بعض الأجهزة تدعم تشغيل فيديوهات 2K / 4K</string>
|
||||
<string name="default_video_format_title">تنسيق الفيديو الافتراضي</string>
|
||||
<string name="popup_remember_size_pos_title">تذكر حجم النافذة و وضعها</string>
|
||||
<string name="popup_remember_size_pos_summary">تذكر آخر حجم ومكان النافذة</string>
|
||||
<string name="player_gesture_controls_title">اعدادات إيماءة المشغل</string>
|
||||
<string name="player_gesture_controls_summary">استخدم إيماءات للتحكم في سطوع وحجم المشغل</string>
|
||||
<string name="show_search_suggestions_title">اقتراحات البحث</string>
|
||||
<string name="show_search_suggestions_summary">عرض الاقتراحات عند البحث</string>
|
||||
<string name="enable_search_history_title">سجل البحث</string>
|
||||
<string name="enable_search_history_summary">تخزين طلبات البحث محليا</string>
|
||||
<string name="enable_watch_history_summary">تتبع مقاطع الفيديو التي تمت مشاهدتها</string>
|
||||
<string name="resume_on_audio_focus_gain_title">استئناف عند اكساب التركيز</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية)</string>
|
||||
<string name="show_hold_to_append_title">إظهار تعليق لإلحاق تلميح</string>
|
||||
<string name="show_hold_to_append_summary">عرض تلميح أو زر منبثق عند الضغط على خلفية على صفحة تفاصيل الفيديو</string>
|
||||
<string name="settings_category_player_title">المشغل</string>
|
||||
<string name="settings_category_player_behavior_title">السلوك</string>
|
||||
<string name="settings_category_popup_title">المنبثق</string>
|
||||
<string name="popup_playing_toast">التشغيل في الوضع المنبثق</string>
|
||||
<string name="background_player_append">تم وضعه على قائمة الانتظار في مشغل الخلفية</string>
|
||||
<string name="popup_playing_append">تم وضعه على قائمة الانتظار في مشغل النافذة المنبثقة</string>
|
||||
<string name="show_age_restricted_content_title">عرض المحتوى المقيد بحسب العمر</string>
|
||||
<string name="video_is_age_restricted">فيديو مقيد بحسب العمر. السماح بهذه المحتوى ممكن من الإعدادات.</string>
|
||||
<string name="duration_live">مباشر</string>
|
||||
<string name="error_report_title">تقرير الخطأ</string>
|
||||
<string name="playlist">قائمة التشغيل</string>
|
||||
<string name="yes">نعم</string>
|
||||
<string name="later">فيما بعد</string>
|
||||
<string name="disabled">معطل</string>
|
||||
<string name="filter">فلتر</string>
|
||||
<string name="refresh">تحديث</string>
|
||||
<string name="clear">تنظيف</string>
|
||||
<string name="popup_resizing_indicator_title">تغيير الحجم</string>
|
||||
<string name="best_resolution">أفضل دقة</string>
|
||||
<string name="undo">تراجع</string>
|
||||
<string name="play_all">تشغيل الكل</string>
|
||||
|
||||
<string name="notification_channel_name">تنبيهات NewPipe</string>
|
||||
<string name="notification_channel_description">التنبيهات لمشغل NewPipe للخلفية والنوافذ المنبثقة</string>
|
||||
|
||||
<string name="unknown_content">[غير معروف]</string>
|
||||
|
||||
<string name="light_parsing_error">لايمكن تحليل الموقع بشكل كلي</string>
|
||||
<string name="could_not_setup_download_menu">يتعذر إعداد قائمة التنزيل</string>
|
||||
<string name="live_streams_not_supported">هذا هو بث مباشر ، وهو غير معتمد حتى الآن.</string>
|
||||
<string name="could_not_get_stream">يتعذر الحصول على أي بث</string>
|
||||
<string name="could_not_load_image">تعذر تحميل الصورة</string>
|
||||
<string name="app_ui_crash">تعطل التطبيق / واجهة المستخدم</string>
|
||||
<string name="player_stream_failure">أخفق تشغيل هذا البث</string>
|
||||
<string name="player_unrecoverable_failure">حدث خطأ المشغل غير قابل للاسترداد</string>
|
||||
<string name="player_recoverable_failure">استرداد المشغل من الخطأ</string>
|
||||
|
||||
<string name="sorry_string">عذرا، لا ينبغي أن يحدث ذلك.</string>
|
||||
<string name="error_report_button_text">الإبلاغ عن خطأ عبر البريد الإلكتروني</string>
|
||||
<string name="error_snackbar_message">عذرا، حدثت بعض الأخطاء.</string>
|
||||
<string name="error_snackbar_action">تقرير</string>
|
||||
<string name="what_device_headline">معلومات:</string>
|
||||
<string name="what_happened_headline">ماذا حدث:</string>
|
||||
<string name="info_labels"></string>
|
||||
<string name="your_comment">تعليقك (باللغة الإنجليزية):</string>
|
||||
<string name="error_details_headline">تفاصيل:</string>
|
||||
|
||||
|
||||
<string name="report_error">الإبلاغ عن خطأ</string>
|
||||
<string name="user_report">تقرير المستخدم</string>
|
||||
<string name="search_no_results">لم يتم العثور على نتائج</string>
|
||||
<string name="empty_subscription_feed_subtitle">لا شيء هنا غير صراصير الليل</string>
|
||||
|
||||
<string name="audio">الصوت</string>
|
||||
<string name="retry">إعادة المحاولة</string>
|
||||
<string name="storage_permission_denied">تم رفض إذن الوصول إلى التخزين</string>
|
||||
<string name="use_old_player_title">استخدام المشغل القديم</string>
|
||||
<string name="use_old_player_summary">المشغل القديم المدمج في إطار Mediaframework</string>
|
||||
|
||||
<string name="short_thousand">ك</string>
|
||||
<string name="short_million">م</string>
|
||||
<string name="short_billion">ب</string>
|
||||
|
||||
<string name="no_subscribers">صفر لا تقم با الإختيار (في بعض اللغات) لأنها ليست \"حالة خاصة\" للأندرويد</string>
|
||||
<plurals name="subscribers">
|
||||
<item quantity="zero">%s مشترك</item>
|
||||
<item quantity="one">%s مشتركين</item>
|
||||
<item quantity="two">%s مشتركين</item>
|
||||
<item quantity="few">%s مشتركون</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_views">لاتوجد مشاهدات</string>
|
||||
<string name="no_videos">لاتوجد فديوهات</string>
|
||||
<string name="start">بداية</string>
|
||||
<string name="pause">إيقاف</string>
|
||||
<string name="view">شغل</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="checksum">التوقيع</string>
|
||||
|
||||
<string name="add">قطعة</string>
|
||||
<string name="finish">حسنا</string>
|
||||
|
||||
<string name="msg_name">اسم الملف</string>
|
||||
<string name="msg_threads">العمليات</string>
|
||||
<string name="msg_error">الخطأ</string>
|
||||
<string name="msg_server_unsupported">الخادم غير معتمد</string>
|
||||
<string name="msg_exists">الملف موجود مسبقا</string>
|
||||
<string name="msg_url_malform">العنوان غير صحيح أو ان الإنترنت غير متوفر</string>
|
||||
<string name="msg_running">NewPipe يقوم بالتحميل</string>
|
||||
<string name="msg_running_detail">انقر للحصول على التفاصيل</string>
|
||||
<string name="msg_wait">أرجو الإنتظار…</string>
|
||||
<string name="msg_copied">نسخ إلى الحافظة</string>
|
||||
<string name="no_available_dir">الرجاء تحديد مجلد لحفظ التنزيلات</string>
|
||||
<string name="msg_popup_permission">هذا الإذن مطلوب
|
||||
\nللفتح في وضع النافذة المنبثقة</string>
|
||||
|
||||
<string name="reCaptchaActivity">اختبار reCAPTCHA</string>
|
||||
<string name="settings_file_charset_title">الأحرف المسموح بها في أسماء الملفات</string>
|
||||
<string name="settings_file_replacement_character_summary">يتم استبدال الأحرف غير الصالحة بهذه القيمة</string>
|
||||
<string name="settings_file_replacement_character_title">استبدال الحرف</string>
|
||||
|
||||
<string name="charset_letters_and_digits">الحروف والأرقام</string>
|
||||
<string name="charset_most_special_characters">معظم الأحرف الخاصة</string>
|
||||
|
||||
<string name="title_activity_about">معلومات عن NewPipe</string>
|
||||
<string name="action_settings">الإعدادات</string>
|
||||
<string name="title_licenses">تراخيص الجهات الخارجية</string>
|
||||
<string name="error_unable_to_load_license">تعذر تحميل الترخيص</string>
|
||||
<string name="action_open_website">فتح الموقع</string>
|
||||
<string name="tab_contributors">المساهمون</string>
|
||||
<string name="tab_licenses">التراخيص</string>
|
||||
<string name="app_description">واجهة أمامية لليوتوب مجانية مفتوحة المصدر و خفيفة الوزن لنظام التشغيل أندرويد.</string>
|
||||
<string name="contribution_title">ساهم</string>
|
||||
<string name="contribution_encouragement">إذا كان لديك أفكار؛ او ترجمة، او تغييرات على التصميم، او تنظيف وتحسين الكود البرمجي ، أو تغييرات ثقيلة على الكود البرمجي، مساعدتك دائما موضع ترحيب. وكلما تم ذلك كلما كان ذلك أفضل!</string>
|
||||
<string name="view_on_github">عرض على GitHub</string>
|
||||
<string name="donation_title">تبرع</string>
|
||||
<string name="donation_encouragement">يتم تطوير NewPipe من قبل المتطوعين الذين يقضون وقت فراغهم لتقديم أفضل تجربة لك. الآن حان الوقت لإعطاء مرة أخرى للتأكد من المطورين لدينا يمكن أن تجعل NewPipe أكثر و أفضل بينما نتمتع بكوب من جافا!</string>
|
||||
<string name="give_back">تبرع</string>
|
||||
<string name="website_title">الموقع</string>
|
||||
<string name="website_encouragement">للحصول على مزيد من المعلومات وآخر الأخبار حول NewPipe الرجاء زيارة موقعنا على الانترنت.</string>
|
||||
<string name="app_license_title">تراخيص NewPipe</string>
|
||||
<string name="read_full_license">قراءة الترخيص</string>
|
||||
|
||||
|
||||
<string name="title_history_search">البحث</string>
|
||||
<string name="title_history_view">شاهد</string>
|
||||
<string name="history_disabled">تم تعطيل السجل</string>
|
||||
<string name="history_empty">التاريخ فارغ</string>
|
||||
<string name="history_cleared">تم تنظيف التاريخ</string>
|
||||
<string name="item_deleted">تم حذف العنصر</string>
|
||||
<string name="delete_item_search_history">هل تريد حذف هذا العنصر من سجل البحث؟</string>
|
||||
|
||||
<string name="main_page_content">المحتوى</string>
|
||||
<string name="blank_page_summary">صفحة فارغة</string>
|
||||
<string name="subscription_page_summary">صفحة الاشتراك</string>
|
||||
<string name="feed_page_summary">صفحة الخلاصة</string>
|
||||
<string name="channel_page_summary">صفحة القناة</string>
|
||||
<string name="select_a_channel">حدد قناة</string>
|
||||
<string name="no_channel_subscribed_yet">لم يتم الاشتراك في القناة بعد</string>
|
||||
<string name="trending">الترند</string>
|
||||
<string name="top_50">أفضل 50</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_stream_detail">تفاصيل</string>
|
||||
<string name="play_queue_audio_settings">إعدادات الصوت</string>
|
||||
<string name="start_here_on_main">بدء التشغيل هنا</string>
|
||||
<string name="start_here_on_popup">تشغيل هنا في وضع النافذة المنبثقة</string>
|
||||
</resources>
|
||||
|
|
|
@ -242,7 +242,7 @@
|
|||
<string name="app_license_title">Llicencia de NewPipe</string>
|
||||
<string name="contribution_encouragement">Si tienes idees, quies traducir, facer cambeos, llimpiar el códigu u otres coses, l\'ayuda siempres s\'agradez. ¡Cuánto más se faiga, más s\'ameyora!</string>
|
||||
<string name="read_full_license">Lleer llicencia</string>
|
||||
<string name="contribution_title">Collaboración</string>
|
||||
<string name="contribution_title">Collaborar</string>
|
||||
|
||||
<string name="title_activity_history">Historial</string>
|
||||
<string name="title_history_search">Guetao</string>
|
||||
|
@ -253,4 +253,46 @@
|
|||
<string name="history_cleared">Llimpióse l\'historial</string>
|
||||
<string name="item_deleted">Desanicióse l\'elementu</string>
|
||||
<string name="delete_item_search_history">¿Quies desaniciar esti elementu del historial de gueta?</string>
|
||||
</resources>
|
||||
<string name="play_all">Reproducir too</string>
|
||||
|
||||
<string name="player_stream_failure">Nun pudo reproducise esta tresmisión</string>
|
||||
<string name="player_unrecoverable_failure">Asocedió un error irrecuperable del reproductor</string>
|
||||
<string name="player_recoverable_failure">Recuperándose del error del reproductor</string>
|
||||
|
||||
<string name="main_page_content">Conteníu de la páxina principal</string>
|
||||
<string name="blank_page_summary">Páxina en blancu</string>
|
||||
<string name="kiosk_page_summary">Páxina del quioscu</string>
|
||||
<string name="subscription_page_summary">Páxina de suscripción</string>
|
||||
<string name="feed_page_summary">Páxina de fluxu</string>
|
||||
<string name="channel_page_summary">Páxina de canal</string>
|
||||
<string name="select_a_channel">Seleiciona un canal</string>
|
||||
<string name="no_channel_subscribed_yet">Entá nun hai canales soscritos</string>
|
||||
<string name="select_a_kiosk">Seleiciona un quioscu</string>
|
||||
|
||||
<string name="kiosk">Quioscu</string>
|
||||
<string name="trending">Tendencies</string>
|
||||
<string name="top_50">50 meyores</string>
|
||||
<string name="title_activity_background_player">Reproductor de fondu</string>
|
||||
<string name="play_queue_remove">Desaniciar</string>
|
||||
<string name="play_queue_stream_detail">Detalles</string>
|
||||
<string name="new_and_hot">Nuevo y popular</string>
|
||||
<string name="play_queue_audio_settings">Axustes d\'audiu</string>
|
||||
<string name="show_hold_to_append_title">Amosar conseyu Mantener p\'amestar</string>
|
||||
<string name="show_hold_to_append_summary">Amuesa\'l conseyu al primir el botón de Ventanu o tener el videu de fondu na so páxina de detalles</string>
|
||||
<string name="background_player_append">Púnxose na cola del reproductor de fondu</string>
|
||||
<string name="popup_playing_append">Púnxose na cola nel reproductor de ventanu</string>
|
||||
<string name="unknown_content">[Desconozse]</string>
|
||||
|
||||
<string name="title_activity_popup_player">Reproductor en ventanu</string>
|
||||
<string name="hold_to_append">Mantener p\'amestar</string>
|
||||
<string name="enqueue_on_background">Púnxose na cola de fondu</string>
|
||||
<string name="enqueue_on_popup">Púnxose na cola del ventanu</string>
|
||||
<string name="start_here_on_main">Entamar reproducción equí</string>
|
||||
<string name="start_here_on_background">Entamar equí de fondu</string>
|
||||
<string name="start_here_on_popup">Entamár equí de ventanu</string>
|
||||
<string name="donation_title">Donar</string>
|
||||
<string name="donation_encouragement">NewPipe ta desendolcáu por voluntarios que gasten el so tiempu llibre pa ufrite la meyor esperiencia. ¡Agora ye momentu de devolve-yos el favor p\'aseguar l\'ameyoramientu de NewPipe mentanto esfruten d\'un café!</string>
|
||||
<string name="give_back">Devolver favor</string>
|
||||
<string name="website_title">Sitiu web</string>
|
||||
<string name="website_encouragement">Pa consiguir información y les anuncies caberes tocante a NewPipe visita\'l nuesu sitiu web.</string>
|
||||
</resources>
|
||||
|
|