mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-20 11:20:52 +02:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9290ed490f | ||
![]() |
1bda812c75 | ||
![]() |
ab82406c98 | ||
![]() |
be763349df | ||
![]() |
91764ad601 | ||
![]() |
1c4031aa38 | ||
![]() |
c3bc648dd4 | ||
![]() |
c97d794272 | ||
![]() |
8e7d2e91e9 | ||
![]() |
cf0fbbbd3d | ||
![]() |
7d3ede7946 | ||
![]() |
1aa308eb5d | ||
![]() |
4bfd30e34a | ||
![]() |
0f46c90688 | ||
![]() |
aa2173fc4a | ||
![]() |
932cb1d19c | ||
![]() |
18b038d8e4 | ||
![]() |
2ac71c75c0 | ||
![]() |
4067ba5232 | ||
![]() |
ab47dd5a5b | ||
![]() |
3130910307 | ||
![]() |
22113439a4 | ||
![]() |
2d25a9ad7a | ||
![]() |
8f0a2cc2f0 | ||
![]() |
4ca39258c9 | ||
![]() |
d0c0238b8b | ||
![]() |
f9f08e5169 | ||
![]() |
cfc51b2401 | ||
![]() |
54c2704cb5 | ||
![]() |
9b51c946fe | ||
![]() |
06f38cbcb4 | ||
![]() |
f368d6b257 | ||
![]() |
f4301da14d | ||
![]() |
f34c09f165 | ||
![]() |
6d1db56512 | ||
![]() |
b70c07d004 | ||
![]() |
f9f48a5eb6 | ||
![]() |
14e4e73444 | ||
![]() |
f2ce4d2daf | ||
![]() |
468ebdda87 | ||
![]() |
a1f0fb3b14 | ||
![]() |
3c9f4de234 | ||
![]() |
8ae411619f | ||
![]() |
61e5c9121a | ||
![]() |
8a3cf0d5dc | ||
![]() |
ecb5df65ac | ||
![]() |
5f1e98a0d3 | ||
![]() |
3b9a477499 |
@@ -46,6 +46,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
||||
* Search channels
|
||||
* Watch videos from a channel
|
||||
* Orbot/Tor support (not yet directly)
|
||||
* 1080p/2k/4k support
|
||||
|
||||
### Coming Features
|
||||
|
||||
@@ -56,7 +57,6 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
||||
* Search/Watch Playlists
|
||||
* Queeing videos
|
||||
* Subtitles support
|
||||
* 1080p support
|
||||
* livestream support
|
||||
* ... and many more
|
||||
|
||||
|
@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '25.0.0'
|
||||
buildToolsVersion '25.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
versionCode 29
|
||||
versionName "0.9.2"
|
||||
versionCode 30
|
||||
versionName "0.9.3"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -35,17 +35,17 @@ dependencies {
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile 'org.json:json:20160810'
|
||||
|
||||
compile 'com.android.support:appcompat-v7:25.1.0'
|
||||
compile 'com.android.support:support-v4:25.1.0'
|
||||
compile 'com.android.support:design:25.1.0'
|
||||
compile 'com.android.support:recyclerview-v7:25.1.0'
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
compile 'org.jsoup:jsoup:1.8.3'
|
||||
compile 'org.mozilla:rhino:1.7.7'
|
||||
compile 'info.guardianproject.netcipher:netcipher:1.2'
|
||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.google.code.gson:gson:2.4'
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
|
||||
|
@@ -40,7 +40,7 @@
|
||||
android:label="@string/background_player_name" />
|
||||
|
||||
<activity
|
||||
android:name=".player.ExoPlayerActivity"
|
||||
android:name=".player.MainVideoPlayer"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
|
@@ -20,10 +20,6 @@ package org.schabi.newpipe;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Singleton:
|
||||
* Used to send data between certain Activity/Services within the same process.
|
||||
@@ -39,8 +35,5 @@ public class ActivityCommunicator {
|
||||
return activityCommunicator;
|
||||
}
|
||||
|
||||
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
|
||||
public volatile Bitmap backgroundPlayerThumbnail;
|
||||
|
||||
public volatile Class returnActivity;
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
|
||||
private static final String TAG = MainActivity.class.toString();
|
||||
//private static final String TAG = "MainActivity";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Activity's LifeCycle
|
||||
@@ -57,12 +57,23 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
if (savedInstanceState == null) initFragments();
|
||||
|
||||
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (intent != null) {
|
||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||
// to not destroy the already created backstack
|
||||
String action = intent.getAction();
|
||||
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
|
||||
}
|
||||
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
|
Submodule app/src/main/java/org/schabi/newpipe/extractor updated: b587d175bb...08457de763
@@ -413,4 +413,9 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker.
|
||||
public void onError(int messageId) {
|
||||
Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnrecoverableError(Exception exception) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
@@ -88,7 +88,7 @@ class ActionBarHandler {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
|
||||
}
|
||||
int defaultResolution = Utils.getPreferredResolution(activity, videoStreams);
|
||||
int defaultResolution = Utils.getDefaultResolution(activity, videoStreams);
|
||||
|
||||
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
|
||||
android.R.layout.simple_spinner_dropdown_item, itemArray);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
717
app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
Normal file
717
app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
public interface StateInterface {
|
||||
int STATE_LOADING = 123;
|
||||
int STATE_PLAYING = 124;
|
||||
int STATE_BUFFERING = 125;
|
||||
int STATE_PAUSED = 126;
|
||||
int STATE_PAUSED_SEEK = 127;
|
||||
int STATE_COMPLETED = 128;
|
||||
|
||||
void changeState(int state);
|
||||
|
||||
void onLoading();
|
||||
void onPlaying();
|
||||
void onBuffering();
|
||||
void onPaused();
|
||||
void onPausedSeek();
|
||||
void onCompleted();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -121,6 +121,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}).show();
|
||||
@@ -130,6 +131,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -180,7 +182,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
intent.putExtra(ERROR_LIST, el);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,6 @@ import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
|
||||
@@ -53,6 +52,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
SharedPreferences.OnSharedPreferenceChangeListener prefListener;
|
||||
// get keys
|
||||
String DEFAULT_RESOLUTION_PREFERENCE;
|
||||
String DEFAULT_POPUP_RESOLUTION_PREFERENCE;
|
||||
String PREFERRED_VIDEO_FORMAT_PREFERENCE;
|
||||
String DEFAULT_AUDIO_FORMAT_PREFERENCE;
|
||||
String SEARCH_LANGUAGE_PREFERENCE;
|
||||
@@ -61,6 +61,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
String USE_TOR_KEY;
|
||||
String THEME;
|
||||
private ListPreference defaultResolutionPreference;
|
||||
private ListPreference defaultPopupResolutionPreference;
|
||||
private ListPreference preferredVideoFormatPreference;
|
||||
private ListPreference defaultAudioFormatPreference;
|
||||
private ListPreference searchLanguagePreference;
|
||||
@@ -80,6 +81,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
|
||||
// get keys
|
||||
DEFAULT_RESOLUTION_PREFERENCE = getString(R.string.default_resolution_key);
|
||||
DEFAULT_POPUP_RESOLUTION_PREFERENCE = getString(R.string.default_popup_resolution_key);
|
||||
PREFERRED_VIDEO_FORMAT_PREFERENCE = getString(R.string.preferred_video_format_key);
|
||||
DEFAULT_AUDIO_FORMAT_PREFERENCE = getString(R.string.default_audio_format_key);
|
||||
SEARCH_LANGUAGE_PREFERENCE = getString(R.string.search_language_key);
|
||||
@@ -91,6 +93,8 @@ public class SettingsFragment extends PreferenceFragment
|
||||
// get pref objects
|
||||
defaultResolutionPreference =
|
||||
(ListPreference) findPreference(DEFAULT_RESOLUTION_PREFERENCE);
|
||||
defaultPopupResolutionPreference =
|
||||
(ListPreference) findPreference(DEFAULT_POPUP_RESOLUTION_PREFERENCE);
|
||||
preferredVideoFormatPreference =
|
||||
(ListPreference) findPreference(PREFERRED_VIDEO_FORMAT_PREFERENCE);
|
||||
defaultAudioFormatPreference =
|
||||
@@ -103,6 +107,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
|
||||
final String currentTheme = defaultPreferences.getString(THEME, "Light");
|
||||
|
||||
// TODO: Clean this, as the class is already implementing the class
|
||||
// and those double equals...
|
||||
|
||||
prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
@@ -260,6 +267,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
defaultResolutionPreference.setSummary(
|
||||
defaultPreferences.getString(DEFAULT_RESOLUTION_PREFERENCE,
|
||||
getString(R.string.default_resolution_value)));
|
||||
defaultPopupResolutionPreference.setSummary(
|
||||
defaultPreferences.getString(DEFAULT_POPUP_RESOLUTION_PREFERENCE,
|
||||
getString(R.string.default_popup_resolution_value)));
|
||||
preferredVideoFormatPreference.setSummary(
|
||||
defaultPreferences.getString(PREFERRED_VIDEO_FORMAT_PREFERENCE,
|
||||
getString(R.string.preferred_video_format_default)));
|
||||
|
@@ -8,12 +8,60 @@ import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnItemSelectedListener;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.VideoPlayer;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class NavigationHelper {
|
||||
|
||||
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
|
||||
Intent mIntent = new Intent(context, targetClazz)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
|
||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
|
||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
|
||||
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
|
||||
return new Intent(context, targetClazz)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
|
||||
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, instance.getChannelName())
|
||||
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
||||
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
|
||||
}
|
||||
|
||||
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
|
||||
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(Utils.getPreferredAudioFormat(context, info.audio_streams)));
|
||||
}
|
||||
|
||||
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
|
||||
Intent mIntent = new Intent(context, BackgroundPlayer.class)
|
||||
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
|
||||
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Through Interface (faster)
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@@ -9,28 +9,31 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Utils {
|
||||
|
||||
private static final List<String> HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
||||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the
|
||||
* preferred resolution and format chosen in the settings
|
||||
* Return the index of the default stream in the list, based on the parameters
|
||||
* defaultResolution and preferredFormat
|
||||
*
|
||||
* @param videoStreams the list that will be extracted the index
|
||||
*
|
||||
* @param videoStreams the list that will be extracted the index
|
||||
* @return index of the preferred resolution&format
|
||||
*/
|
||||
public static int getPreferredResolution(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
public static int getDefaultResolution(String defaultResolution, String preferredFormat, List<VideoStream> videoStreams) {
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(context.getString(R.string.default_resolution_key),
|
||||
context.getString(R.string.default_resolution_value));
|
||||
|
||||
String preferredFormat = defaultPreferences
|
||||
.getString(context.getString(R.string.preferred_video_format_key),
|
||||
context.getString(R.string.preferred_video_format_default));
|
||||
if (defaultResolution.equals("Best resolution")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// first try to find the one with the right resolution
|
||||
int selectedFormat = 0;
|
||||
@@ -50,16 +53,69 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFormat == 0 && !videoStreams.get(selectedFormat).resolution.contains(defaultResolution.replace("p60", "p"))) {
|
||||
// Maybe there's no 60 fps variant available, so fallback to the normal version
|
||||
String replace = defaultResolution.replace("p60", "p");
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (replace.equals(item.resolution)) selectedFormat = i;
|
||||
}
|
||||
|
||||
// than try to find the one with the right resolution and format
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
VideoStream item = videoStreams.get(i);
|
||||
if (replace.equals(item.resolution)
|
||||
&& preferredFormat.equals(MediaFormat.getNameById(item.format))) {
|
||||
selectedFormat = i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// this is actually an error,
|
||||
// but maybe there is really no stream fitting to the default value.
|
||||
return selectedFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolution(String, String, List)
|
||||
*/
|
||||
public static int getDefaultResolution(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value));
|
||||
|
||||
String preferredFormat = defaultPreferences
|
||||
.getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
|
||||
return getDefaultResolution(defaultResolution, preferredFormat, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolution(String, String, List)
|
||||
*/
|
||||
public static int getPopupDefaultResolution(Context context, List<VideoStream> videoStreams) {
|
||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (defaultPreferences == null) return 0;
|
||||
|
||||
String defaultResolution = defaultPreferences
|
||||
.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value));
|
||||
|
||||
String preferredFormat = defaultPreferences
|
||||
.getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
|
||||
return getDefaultResolution(defaultResolution, preferredFormat, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the
|
||||
* preferred audio format chosen in the settings
|
||||
*
|
||||
* @param context context to get the preferred audio format
|
||||
* @param audioStreams the list that will be extracted the index
|
||||
*
|
||||
* @return index of the preferred format
|
||||
*/
|
||||
public static int getPreferredAudioFormat(Context context, List<AudioStream> audioStreams) {
|
||||
@@ -80,12 +136,138 @@ public class Utils {
|
||||
break;
|
||||
}
|
||||
|
||||
int highestQualityIndex = 0;
|
||||
|
||||
// Try to find a audio stream with the preferred format
|
||||
for (int i = 0; i < audioStreams.size(); i++) if (audioStreams.get(i).format == preferredFormat) highestQualityIndex = i;
|
||||
|
||||
// Try to find a audio stream with the highest bitrate and preferred format
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
if (audioStreams.get(i).format == preferredFormat) {
|
||||
return i;
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate
|
||||
&& audioStream.format == preferredFormat) highestQualityIndex = i;
|
||||
}
|
||||
|
||||
return highestQualityIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the highest bitrate
|
||||
*
|
||||
* @param audioStreams list the audio streams
|
||||
* @return audio with highest average bitrate
|
||||
*/
|
||||
public static AudioStream getHighestQualityAudio(List<AudioStream> audioStreams) {
|
||||
int highestQualityIndex = 0;
|
||||
|
||||
for (int i = 1; i < audioStreams.size(); i++) {
|
||||
AudioStream audioStream = audioStreams.get(i);
|
||||
if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate) highestQualityIndex = i;
|
||||
}
|
||||
|
||||
return audioStreams.get(highestQualityIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param context context to search for the format to give preference
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static ArrayList<VideoStream> getSortedStreamVideosList(Context context, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
boolean showHigherResolutions = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.show_higher_resolutions_key), false);
|
||||
String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.preferred_video_format_key), context.getString(R.string.preferred_video_format_default));
|
||||
MediaFormat preferredFormat = MediaFormat.WEBM;
|
||||
switch (preferredFormatString) {
|
||||
case "WebM":
|
||||
preferredFormat = MediaFormat.WEBM;
|
||||
break;
|
||||
case "MPEG-4":
|
||||
preferredFormat = MediaFormat.MPEG_4;
|
||||
break;
|
||||
case "3GPP":
|
||||
preferredFormat = MediaFormat.v3GPP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
||||
* chosen by the user
|
||||
*
|
||||
* @param preferredFormat format to give preference
|
||||
* @param showHigherResolutions show >1080p resolutions
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static ArrayList<VideoStream> getSortedStreamVideosList(MediaFormat preferredFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
ArrayList<VideoStream> retList = new ArrayList<>();
|
||||
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
|
||||
if (videoOnlyStreams != null) {
|
||||
for (VideoStream stream : videoOnlyStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
if (videoStreams != null) {
|
||||
for (VideoStream stream : videoStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue;
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Add all to the hashmap
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream);
|
||||
|
||||
// Override the values when the key == resolution, with the preferredFormat
|
||||
for (VideoStream videoStream : retList) {
|
||||
if (videoStream.format == preferredFormat.id) hashMap.put(videoStream.resolution, videoStream);
|
||||
}
|
||||
|
||||
retList.clear();
|
||||
retList.addAll(hashMap.values());
|
||||
sortStreamList(retList, ascendingOrder);
|
||||
return retList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the streams list depending on the parameter ascendingOrder;
|
||||
* <p>
|
||||
* It works like that:<br>
|
||||
* - Take a string resolution, remove the letters, replace "0p60" (for 60fps videos) with "1"
|
||||
* and sort by the greatest:<br>
|
||||
* <blockquote><pre>
|
||||
* 720p -> 720
|
||||
* 720p60 -> 721
|
||||
* 360p -> 360
|
||||
* 1080p -> 1080
|
||||
* 1080p60 -> 1081
|
||||
* <p>
|
||||
* ascendingOrder ? 360 < 720 < 721 < 1080 < 1081
|
||||
* !ascendingOrder ? 1081 < 1080 < 721 < 720 < 360/pre></blockquote>
|
||||
* <p>
|
||||
* @param videoStreams list that the sorting will be applied
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
*/
|
||||
public static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
||||
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.]", ""));
|
||||
|
||||
return ascendingOrder ? res1 - res2 : res2 - res1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,11 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||
public interface OnChannelInfoReceive {
|
||||
void onReceive(ChannelInfo info);
|
||||
void onError(int messageId);
|
||||
/**
|
||||
* Called when an unrecoverable error has occurred.
|
||||
* <p> This is a good place to finish the caller. </p>
|
||||
*/
|
||||
void onUnrecoverableError(Exception exception);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,9 +79,11 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleException(Exception exception, int serviceId, String url) {
|
||||
protected void handleException(final Exception exception, int serviceId, String url) {
|
||||
if (callback == null || getHandler() == null || isInterrupted()) return;
|
||||
|
||||
if (exception instanceof IOException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onError(R.string.network_error);
|
||||
@@ -84,10 +91,20 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||
});
|
||||
} else if (exception instanceof ParsingException || exception instanceof ExtractionException) {
|
||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
|
||||
finishIfActivity();
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
|
||||
finishIfActivity();
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -135,13 +135,6 @@ public abstract class ExtractorWorker extends Thread {
|
||||
this.service = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the context passed in the constructor is an {@link Activity}, finish it.
|
||||
*/
|
||||
protected void finishIfActivity() {
|
||||
if (getContext() instanceof Activity) ((Activity) getContext()).finish();
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
@@ -35,6 +35,12 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||
void onBlockedByGemaError();
|
||||
void onContentErrorWithMessage(int messageId);
|
||||
void onContentError();
|
||||
|
||||
/**
|
||||
* Called when an unrecoverable error has occurred.
|
||||
* <p> This is a good place to finish the caller. </p>
|
||||
*/
|
||||
void onUnrecoverableError(Exception exception);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +68,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||
|
||||
if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, ErrorActivity.REQUESTED_STREAM);
|
||||
|
||||
if (callback != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
||||
if (callback != null && getHandler() != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isInterrupted() || callback == null) return;
|
||||
@@ -75,37 +81,39 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleException(final Exception exception, int serviceId, String url) {
|
||||
protected void handleException(final Exception exception, int serviceId, final String url) {
|
||||
if (callback == null || getHandler() == null || isInterrupted()) return;
|
||||
|
||||
if (exception instanceof ReCaptchaException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onReCaptchaException();
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof IOException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onError(R.string.network_error);
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof YoutubeStreamExtractor.GemaException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onBlockedByGemaError();
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onContentErrorWithMessage(R.string.live_streams_not_supported);
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof StreamExtractor.ContentNotAvailableException) {
|
||||
if (callback != null) getHandler().post(new Runnable() {
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onContentError();
|
||||
@@ -114,7 +122,12 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||
} else if (exception instanceof YoutubeStreamExtractor.DecryptException) {
|
||||
// custom service related exceptions
|
||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
|
||||
finishIfActivity();
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof StreamInfo.StreamExctractException) {
|
||||
if (!streamInfo.errors.isEmpty()) {
|
||||
// !!! if this case ever kicks in someone gets kicked out !!!
|
||||
@@ -122,13 +135,29 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||
} else {
|
||||
ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
|
||||
}
|
||||
finishIfActivity();
|
||||
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
} else if (exception instanceof ParsingException) {
|
||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
|
||||
finishIfActivity();
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
|
||||
finishIfActivity();
|
||||
getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onUnrecoverableError(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
17
app/src/main/res/drawable/custom_progress_bar.xml
Normal file
17
app/src/main/res/drawable/custom_progress_bar.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<solid android:color="@color/gray"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="@color/middle_gray"/>
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
@@ -1,16 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/notificationContent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/background_notification_color">
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/notificationContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:background="@color/background_notification_color"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
@@ -18,65 +19,102 @@
|
||||
android:id="@+id/notificationCover"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/dummy_thumbnail"
|
||||
android:scaleType="centerCrop"/>
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text="title" />
|
||||
android:maxLines="1"
|
||||
android:textSize="14sp"
|
||||
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:layout_width="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text="artist" />
|
||||
android:maxLines="1"
|
||||
android:textSize="12sp"
|
||||
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationRewind"
|
||||
android:id="@+id/notificationRepeat"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_action_av_fast_rewind" />
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_repeat_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationFRewind"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationPlayPause"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_pause_white" />
|
||||
android:src="@drawable/ic_pause_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationFForward"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_forward"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationStop"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_close_white" />
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_close_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/notificationProgressBar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginLeft="64dp"
|
||||
android:progressDrawable="@drawable/custom_progress_bar"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:progress="52"/>
|
||||
</FrameLayout>
|
@@ -1,4 +1,153 @@
|
||||
<?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/notificationContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:background="@color/background_notification_color"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/notificationCover"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/dummy_thumbnail"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationStop"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_close_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@+id/notificationStop"
|
||||
android:layout_toRightOf="@+id/notificationCover"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||
|
||||
<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"
|
||||
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"
|
||||
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/notificationProgressBar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_alignTop="@+id/notificationControls"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_toRightOf="@+id/notificationCover"
|
||||
android:progressDrawable="@drawable/custom_progress_bar"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:progress="52"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/notificationControls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toRightOf="@+id/notificationCover"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationRepeat"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingLeft="11dp"
|
||||
android:paddingRight="11dp"
|
||||
android:paddingTop="4dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_repeat_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationFRewind"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_toLeftOf="@+id/notificationPlayPause"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationPlayPause"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_toLeftOf="@+id/notificationFForward"
|
||||
android:background="#00000000"
|
||||
android:padding="2dp"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationFForward"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_forward"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/notificationContent"
|
||||
android:layout_width="fill_parent"
|
||||
@@ -93,4 +242,4 @@
|
||||
android:layout_alignParentLeft="true" />
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>-->
|
@@ -29,20 +29,23 @@
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:maxLines="1"
|
||||
tools:text="a long, long, long, long, long title"/>
|
||||
android:textSize="14sp"
|
||||
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:layout_width="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:maxLines="1"
|
||||
tools:text="a long, long artist"/>
|
||||
android:textSize="12sp"
|
||||
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<string name="background_player_name">Reproductor de fondu NewPipe</string>
|
||||
<string name="view_count_text">%1$s visiones</string>
|
||||
<string name="upload_date_text">Espublizáu\'l %1$s</string>
|
||||
<string name="no_player_found">Nun s\'alcontró\'l reproductor de tresmisiones. ¿Instalar VLC?</string>
|
||||
<string name="no_player_found">Nun s\'alcontró un reproductor de fluxos. ¿Quies instalar VLC?</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="cancel">Encaboxar</string>
|
||||
<string name="open_in_browser">Abrir nel restolador</string>
|
||||
@@ -129,7 +129,7 @@
|
||||
<string name="msg_exists">Yá esiste\'l ficheru</string>
|
||||
<string name="msg_url_malform">URL malformada o internet non disponible</string>
|
||||
<string name="msg_running_detail">Calca pa detallles</string>
|
||||
<string name="msg_wait">Espera, por favor...</string>
|
||||
<string name="msg_wait">Espera, por favor…</string>
|
||||
<string name="msg_copied">Copióse al cartafueyu.</string>
|
||||
<string name="no_available_dir">Esbilla un direutoriu de descarga disponible, por favor.</string>
|
||||
|
||||
@@ -147,5 +147,40 @@
|
||||
<string name="search_page">"Guetar páxina: "</string>
|
||||
<string name="could_not_load_image">Nun pudo cargase la imaxe</string>
|
||||
<string name="app_ui_crash">Cascó l\'aplicación/IU</string>
|
||||
<string name="info_labels"></string>
|
||||
<string name="info_labels"/>
|
||||
<string name="open_in_popup_mode">Abrir en ventanu emerxente</string>
|
||||
<string name="popup_mode_share_menu_title">Mou de ventanu emerxente de NewPipe</string>
|
||||
|
||||
<string name="preferred_video_format_title">Formatu preferíu de videu</string>
|
||||
<string name="black_theme_title">Prietu</string>
|
||||
|
||||
<string name="popup_playing_toast">Reproduciendo en ventanu emerxente</string>
|
||||
<string name="all">Too</string>
|
||||
<string name="channel">Canal</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="later">Más sero</string>
|
||||
<string name="disabled">Deshabilitóse</string>
|
||||
|
||||
<string name="use_old_player_title">Usar reproductor vieyu</string>
|
||||
<string name="videos">vídeos</string>
|
||||
<string name="subscriber">soscriptor</string>
|
||||
<string name="subscriber_plural">soscriptores</string>
|
||||
<string name="subscribe">Soscribise</string>
|
||||
<string name="views">visiones</string>
|
||||
<string name="short_thousand">M</string>
|
||||
<string name="short_million">Mill</string>
|
||||
<string name="short_billion">MMill</string>
|
||||
<string name="restart_title">Reaniciar</string>
|
||||
|
||||
<string name="msg_restart">Tienes de reaniciar l\'aplicación p\'aplicar el tema.
|
||||
|
||||
¿Quies reaniciala agora?</string>
|
||||
<string name="msg_popup_permission">Precísase esti permisu pa
|
||||
abrir en ventanu emerxente</string>
|
||||
|
||||
<string name="action_settings">Axustes</string>
|
||||
<string name="reCaptchaActivity">reCAPTCHA</string>
|
||||
<string name="reCaptcha_title">Prueba reCAPTCHA</string>
|
||||
<string name="recaptcha_request_toast">Prueba reCAPTCHA solicitada</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -135,7 +135,7 @@
|
||||
<string name="switch_mode">Zwischen Liste und Gitter umschalten</string>
|
||||
|
||||
<string name="videos">Videos</string>
|
||||
<string name="subscriber">Abonnenten</string>
|
||||
<string name="subscriber">Abonnent</string>
|
||||
<string name="views">Aufrufe</string>
|
||||
<string name="short_thousand">Tsd.</string>
|
||||
<string name="short_million">Mio.</string>
|
||||
@@ -186,4 +186,17 @@ Möchten Sie jetzt neu starten?</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
|
||||
<string name="use_old_player_title">Benutze den alten Player</string>
|
||||
<string name="open_in_popup_mode">Im Popup-Modus öffnen</string>
|
||||
<string name="preferred_video_format_title">Bevorzugtes Videoformat</string>
|
||||
<string name="popup_playing_toast">Spiele im Popup-Modus ab</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe Popup-Modus</string>
|
||||
|
||||
<string name="subscriber_plural">Abonnenten</string>
|
||||
<string name="msg_popup_permission">Diese Berechtigung ist für das
|
||||
Öffnen im Popup-Modus erforderlich</string>
|
||||
|
||||
<string name="use_old_player_summary">Mediaframework Player der vorherigen Version.</string>
|
||||
<string name="default_popup_resolution_title">Standardauflösung des Popups</string>
|
||||
<string name="show_higher_resolutions_title">Zeige höhere Auflösungen an</string>
|
||||
<string name="show_higher_resolutions_summary">Nur einige Geräte unterstützen das Abspielen von 2k-/4k-Videos</string>
|
||||
</resources>
|
||||
|
@@ -19,11 +19,11 @@
|
||||
<string name="download_path_title">Ruta de descarga de vídeo</string>
|
||||
<string name="download_path_summary">Ruta para almacenar los vídeos descargados.</string>
|
||||
<string name="download_path_dialog_title">Introducir directorio de descargas para vídeos</string>
|
||||
<string name="default_resolution_title">Resolución por defecto</string>
|
||||
<string name="default_resolution_title">Resolución de vídeo por defecto</string>
|
||||
<string name="play_with_kodi_title">Reproducir con Kodi</string>
|
||||
<string name="kore_not_found">Aplicación Kore no encontrada. ¿Instalar Kore?</string>
|
||||
<string name="show_play_with_kodi_title">Mostrar opción \"Reproducir con Kodi\"</string>
|
||||
<string name="show_play_with_kodi_summary">Muestra una opción para reproducir el vídeo con Kodi Media Center.</string>
|
||||
<string name="show_play_with_kodi_summary">Mostrar una opción para reproducir el vídeo con Kodi Media Center.</string>
|
||||
<string name="play_audio">Audio</string>
|
||||
<string name="default_audio_format_title">Formato de audio por defecto</string>
|
||||
<string name="webm_description">WebM — formato libre</string>
|
||||
@@ -131,13 +131,13 @@
|
||||
<string name="checksum">Checksum</string>
|
||||
|
||||
<string name="add">Nueva misión</string>
|
||||
<string name="finish">Listo</string>
|
||||
<string name="finish">Ok</string>
|
||||
<string name="switch_mode">Cambiar entre lista y cuadrícula</string>
|
||||
|
||||
|
||||
<string name="msg_url">URL de descarga</string>
|
||||
<string name="msg_name">Nombre del archivo</string>
|
||||
<string name="msg_threads">Hilos de conexión</string>
|
||||
<string name="msg_threads">Conexiones simultáneas</string>
|
||||
<string name="msg_fetch_filename">Obtener nombre de archivo</string>
|
||||
<string name="msg_error">Error</string>
|
||||
<string name="msg_server_unsupported">Servidor no soportado</string>
|
||||
@@ -191,4 +191,8 @@ abrir en modo popup</string>
|
||||
<string name="preferred_video_format_title">Formato de vídeo preferido</string>
|
||||
<string name="disabled">Desactivado</string>
|
||||
|
||||
<string name="subscriber_plural">Suscriptores</string>
|
||||
<string name="show_higher_resolutions_title">Mostrar resoluciones más altas</string>
|
||||
<string name="show_higher_resolutions_summary">Solo algunos dispositivos soportan reproducción de vídeos de 2k/4k</string>
|
||||
<string name="default_popup_resolution_title">Resolución predeterminada del popup</string>
|
||||
</resources>
|
||||
|
@@ -190,4 +190,8 @@ membuka di mode popup</string>
|
||||
<string name="disabled">Dinonaktifkan</string>
|
||||
|
||||
<string name="preferred_video_format_title">Pilihan format video</string>
|
||||
<string name="subscriber_plural">subscriber</string>
|
||||
<string name="default_popup_resolution_title">Resolusi popup bawaan</string>
|
||||
<string name="show_higher_resolutions_title">Tampilkan resolusi yang lebih tinggi</string>
|
||||
<string name="show_higher_resolutions_summary">Hanya perangkat tertentu yang mendukung pemutaran video 2k/4k</string>
|
||||
</resources>
|
||||
|
@@ -197,4 +197,8 @@
|
||||
<string name="disabled">無効</string>
|
||||
|
||||
<string name="preferred_video_format_title">お好みのビデオ フォーマット</string>
|
||||
<string name="subscriber_plural">購読者</string>
|
||||
<string name="default_popup_resolution_title">デフォルトのポップアップ解像度</string>
|
||||
<string name="show_higher_resolutions_title">高い解像度で表示</string>
|
||||
<string name="show_higher_resolutions_summary">一部のデバイスのみ 2K/4K ビデオの再生をサポートしています</string>
|
||||
</resources>
|
||||
|
@@ -188,4 +188,8 @@ te openen in pop-upmodus</string>
|
||||
<string name="preferred_video_format_title">Voorkeursvideoformaat</string>
|
||||
<string name="disabled">Uitgeschakeld</string>
|
||||
|
||||
<string name="subscriber_plural">abonnees</string>
|
||||
<string name="default_popup_resolution_title">Standaardresolutie voor pop-up</string>
|
||||
<string name="show_higher_resolutions_title">Hogere resoluties weergeven</string>
|
||||
<string name="show_higher_resolutions_summary">Video\'s afspelen in 2k/4k wordt maar op sommige apparaten ondersteund</string>
|
||||
</resources>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user