1
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-09-20 11:20:52 +02:00

Compare commits

...

48 Commits

Author SHA1 Message Date
anonymous
9290ed490f Translated using Weblate (Turkish)
Currently translated at 100.0% (164 of 164 strings)
2017-04-21 11:12:23 +02:00
monolifed
1bda812c75 Translated using Weblate (Turkish)
Currently translated at 100.0% (164 of 164 strings)
2017-04-21 11:11:23 +02:00
Weblate
ab82406c98 Merge remote-tracking branch 'origin/master' 2017-04-19 17:36:17 +02:00
Mladen Pejaković
be763349df Translated using Weblate (Serbian)
Currently translated at 99.3% (163 of 164 strings)
2017-04-19 17:36:16 +02:00
zmni
91764ad601 Translated using Weblate (Indonesian)
Currently translated at 100.0% (164 of 164 strings)
2017-04-19 17:36:14 +02:00
Christian Schabesberger
1c4031aa38 Merge branch 'master' of github.com:theScrabi/NewPipe 2017-04-18 22:39:16 +02:00
Christian Schabesberger
c3bc648dd4 moved on to version 0.9.3 2017-04-18 22:34:32 +02:00
Christian Schabesberger
c97d794272 updated sdk and support lib 2017-04-18 22:34:04 +02:00
Christian Schabesberger
8e7d2e91e9 Merge branch 'feature-backplayer' of https://github.com/mauriciocolli/NewPipe into mark 2017-04-18 22:06:47 +02:00
Laura Arjona Reina
cf0fbbbd3d Translated using Weblate (Spanish)
Currently translated at 100.0% (164 of 164 strings)
2017-04-18 20:19:12 +02:00
Tobias Groza
7d3ede7946 Translated using Weblate (German)
Currently translated at 100.0% (164 of 164 strings)
2017-04-18 12:58:44 +02:00
Eduardo Caron
1aa308eb5d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (164 of 164 strings)
2017-04-18 00:35:02 +02:00
anonymous
4bfd30e34a Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.3% (163 of 164 strings)
2017-04-18 00:28:43 +02:00
Eduardo Caron
0f46c90688 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.3% (163 of 164 strings)
2017-04-18 00:27:31 +02:00
Matej U
aa2173fc4a Translated using Weblate (Slovenian)
Currently translated at 100.0% (164 of 164 strings)
2017-04-17 18:09:10 +02:00
Mauricio Colli
932cb1d19c Remove ugly bitmap sharing and improvements
- Now it is just using the ImageLoader to load the image, and better yet, it already have cache implemented in it
- Improve MainActivity intent handler
- Improve utils classes
2017-04-17 01:21:48 -03:00
Mauricio Colli
18b038d8e4 Improve notification layouts 2017-04-17 01:20:14 -03:00
Mauricio Colli
2ac71c75c0 Improve players
- Background player is using ExoPlayer internally now
2017-04-17 01:19:53 -03:00
naofum
4067ba5232 Translated using Weblate (Japanese)
Currently translated at 100.0% (164 of 164 strings)
2017-04-16 16:48:37 +02:00
Marian Hanzel
ab47dd5a5b Translated using Weblate (Slovak)
Currently translated at 100.0% (164 of 164 strings)
2017-04-15 22:32:17 +02:00
Nathan Follens
3130910307 Translated using Weblate (Dutch)
Currently translated at 100.0% (164 of 164 strings)
2017-04-15 17:35:12 +02:00
Sérgio Marques
22113439a4 Translated using Weblate (Portuguese)
Currently translated at 100.0% (164 of 164 strings)
2017-04-15 16:09:58 +02:00
Weblate
2d25a9ad7a Merge remote-tracking branch 'origin/master' 2017-04-15 16:07:22 +02:00
Freddy Morán Jr
8f0a2cc2f0 Translated using Weblate (Spanish)
Currently translated at 100.0% (161 of 161 strings)
2017-04-15 16:07:22 +02:00
Marian Hanzel
4ca39258c9 Translated using Weblate (Slovak)
Currently translated at 100.0% (161 of 161 strings)
2017-04-15 16:07:21 +02:00
Enol P
d0c0238b8b Translated using Weblate (Asturian)
Currently translated at 98.1% (158 of 161 strings)
2017-04-15 16:07:20 +02:00
Sérgio Marques
f9f08e5169 Translated using Weblate (Portuguese)
Currently translated at 100.0% (161 of 161 strings)
2017-04-15 16:07:18 +02:00
Christian Schabesberger
cfc51b2401 Merge branch 'master' of github.com:theScrabi/NewPipe 2017-04-15 14:05:39 +02:00
Christian Schabesberger
54c2704cb5 switch to dark default theme 2017-04-15 14:05:31 +02:00
Christian Schabesberger
9b51c946fe add 1080p to available features 2017-04-15 13:43:21 +02:00
Christian Schabesberger
06f38cbcb4 Merge branch 'feature-4k60f' of git://github.com/mauriciocolli/NewPipe into 4k 2017-04-15 12:52:03 +02:00
Christian Schabesberger
f368d6b257 update extractor 2017-04-15 12:51:35 +02:00
Marian Hanzel
f4301da14d Translated using Weblate (Slovak)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 21:49:26 +02:00
Weblate
f34c09f165 Merge remote-tracking branch 'origin/master' 2017-04-13 20:51:06 +02:00
Freddy Morán Jr
6d1db56512 Translated using Weblate (Spanish)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 20:51:03 +02:00
Christian Schabesberger
b70c07d004 update extractor 2017-04-13 19:41:43 +02:00
Christian Schabesberger
f9f48a5eb6 Merge branch 'master' of github.com:theScrabi/NewPipe 2017-04-13 19:41:28 +02:00
Christian Schabesberger
14e4e73444 update gradle 2017-04-13 19:41:15 +02:00
Nathan Follens
f2ce4d2daf Translated using Weblate (Dutch)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 19:10:56 +02:00
naofum
468ebdda87 Translated using Weblate (Japanese)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 16:53:26 +02:00
nautilusx
a1f0fb3b14 Translated using Weblate (German)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 16:08:34 +02:00
zmni
3c9f4de234 Translated using Weblate (Indonesian)
Currently translated at 100.0% (161 of 161 strings)
2017-04-13 15:59:13 +02:00
Tobias Groza
8ae411619f Translated using Weblate (German)
Currently translated at 96.8% (156 of 161 strings)
2017-04-13 15:56:35 +02:00
nautilusx
61e5c9121a Translated using Weblate (German)
Currently translated at 96.2% (155 of 161 strings)
2017-04-13 15:55:28 +02:00
Weblate
8a3cf0d5dc Merge remote-tracking branch 'origin/master' 2017-04-13 15:53:52 +02:00
Freddy Morán Jr
ecb5df65ac Translated using Weblate (Spanish)
Currently translated at 100.0% (160 of 160 strings)
2017-04-13 15:53:52 +02:00
anonymous
5f1e98a0d3 Translated using Weblate (German)
Currently translated at 96.2% (154 of 160 strings)
2017-04-13 15:53:49 +02:00
Mauricio Colli
3b9a477499 Add resolution support up to 4k and 60 fps
- Up to 4k with 60 fps
    - Not every device can play in that resolution and bitrate
    - Add option to hide these high resolution greater than 1080p (2k,4k) for not clutter the menus
- Add a default resolution for the popup, wil be used when opening in popup mode from another app
2017-04-12 03:07:15 -03:00
43 changed files with 2392 additions and 1411 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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)));

View File

@@ -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)
//////////////////////////////////////////////////////////////////////////*/

View File

@@ -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;
}
});
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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;
}

View File

@@ -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);
}
});
}
}

View 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>

View File

@@ -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>

View File

@@ -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>-->

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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