diff --git a/.gitignore b/.gitignore index 09cade508..f6e554c09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ .gitignore .gradle /local.properties -/.idea/workspace.xml -/.idea/libraries .DS_Store /build /captures -.idea/gradle.xml -.idea/misc.xml +/app/app.iml +/.idea diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 0c8ef9fd5..000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -NewPipe \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43efa..000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf337..000000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/the_scrabi.xml b/.idea/dictionaries/the_scrabi.xml deleted file mode 100644 index bc8411615..000000000 --- a/.idea/dictionaries/the_scrabi.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index bd4202cb3..000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 5d1998103..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index ff02d86b4..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d8..000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/NewPipe.iml b/NewPipe.iml deleted file mode 100644 index 8d3b0cd6c..000000000 --- a/NewPipe.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 905208166..648a1e695 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Download audio only (working but, but it could be better) * Open a video in Kodi * Show Next/Related videos +* Search Youtube in a specific language ## Coming Features diff --git a/app/.gitignore b/app/.gitignore index 6bcbd4f70..d9a86a57c 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,3 @@ .gitignore /build -app/app.iml +app.iml diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index 58221a4e6..000000000 --- a/app/app.iml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 5e17108de..fb41271d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 23 - versionCode 5 - versionName "0.5.0" + versionCode 6 + versionName "0.6.0" } buildTypes { release { diff --git a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java index 5cdecef66..b9bef5cfd 100644 --- a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java +++ b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java @@ -77,7 +77,7 @@ public class ActionBarHandler { int defaultResolutionPos = 0; for(int i = 0; i < videoStreams.length; i++) { - itemArray[i] = VideoInfo.getNameById(videoStreams[i].format) + " " + videoStreams[i].resolution; + itemArray[i] = MediaFormat.getNameById(videoStreams[i].format) + " " + videoStreams[i].resolution; if(defaultResolution.equals(videoStreams[i].resolution)) { defaultResolutionPos = i; } @@ -98,17 +98,15 @@ public class ActionBarHandler { .getString(activity.getString(R.string.defaultAudioFormatPreference), "webm"); if(preferedFormat.equals("webm")) { for(VideoInfo.AudioStream s : audioStreams) { - if(s.format == VideoInfo.I_WEBMA) { + if(s.format == MediaFormat.WEBMA.id) { audioStream = s; } } } else if(preferedFormat.equals("m4a")){ for(VideoInfo.AudioStream s : audioStreams) { - Log.d(TAG, VideoInfo.getMimeById(s.format) + " : " + Integer.toString(s.bandwidth)); - if(s.format == VideoInfo.I_M4A && + if(s.format == MediaFormat.M4A.id && (audioStream == null || audioStream.bandwidth > s.bandwidth)) { audioStream = s; - Log.d(TAG, "last choosen"); } } } @@ -125,15 +123,8 @@ public class ActionBarHandler { defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity); inflater.inflate(R.menu.videoitem_detail, menu); - MenuItem playItem = menu.findItem(R.id.menu_item_play); - MenuItem shareItem = menu.findItem(R.id.menu_item_share); MenuItem castItem = menu.findItem(R.id.action_play_with_kodi); - MenuItemCompat.setShowAsAction(playItem, MenuItemCompat.SHOW_AS_ACTION_ALWAYS - | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); - MenuItemCompat.setShowAsAction(shareItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM - | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); - castItem.setVisible(defaultPreferences .getBoolean(activity.getString(R.string.showPlayWidthKodiPreference), false)); @@ -143,9 +134,6 @@ public class ActionBarHandler { public boolean onItemSelected(MenuItem item) { int id = item.getItemId(); switch(id) { - case R.id.menu_item_play: - playVideo(); - return true; case R.id.menu_item_share: if(!videoTitle.isEmpty()) { Intent intent = new Intent(); @@ -196,7 +184,7 @@ public class ActionBarHandler { intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(videoStreams[selectedStream].url), - VideoInfo.getMimeById(videoStreams[selectedStream].format)); + MediaFormat.getMimeById(videoStreams[selectedStream].format)); intent.putExtra(Intent.EXTRA_TITLE, videoTitle); intent.putExtra("title", videoTitle); @@ -235,10 +223,9 @@ public class ActionBarHandler { } public void downloadVideo() { - Log.d(TAG, "bla"); if(!videoTitle.isEmpty()) { - String videoSuffix = "." + VideoInfo.getSuffixById(videoStreams[selectedStream].format); - String audioSuffix = "." + VideoInfo.getSuffixById(audioStream.format); + String videoSuffix = "." + MediaFormat.getSuffixById(videoStreams[selectedStream].format); + String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); Bundle args = new Bundle(); args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix); args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix); @@ -297,7 +284,7 @@ public class ActionBarHandler { try { intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(audioStream.url), - VideoInfo.getMimeById(audioStream.format)); + MediaFormat.getMimeById(audioStream.format)); intent.putExtra(Intent.EXTRA_TITLE, videoTitle); intent.putExtra("title", videoTitle); activity.startActivity(intent); // HERE !!! @@ -321,7 +308,7 @@ public class ActionBarHandler { } }); builder.create().show(); - Log.d(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); + Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); e.printStackTrace(); } } diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index edf839ac2..b6d22c44e 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -1,6 +1,7 @@ package org.schabi.newpipe; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; @@ -29,12 +30,25 @@ import java.net.UnknownHostException; public class Downloader { private static final String USER_AGENT = "Mozilla/5.0"; - public static String download(String siteUrl) { - StringBuffer response = new StringBuffer(); + public static String download(String siteUrl, String language) { + String ret = ""; try { URL url = new URL(siteUrl); HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestProperty("Accept-Language", language); + ret = dl(con); + } + catch(Exception e) { + e.printStackTrace(); + } + return ret; + } + + private static String dl(HttpURLConnection con) { + StringBuffer response = new StringBuffer(); + + try { con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", USER_AGENT); @@ -57,4 +71,20 @@ public class Downloader { } return response.toString(); } + + + public static String download(String siteUrl) { + String ret = ""; + + try { + URL url = new URL(siteUrl); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + ret = dl(con); + } + catch(Exception e) { + e.printStackTrace(); + } + + return ret; + } } diff --git a/app/src/main/java/org/schabi/newpipe/MediaFormat.java b/app/src/main/java/org/schabi/newpipe/MediaFormat.java new file mode 100644 index 000000000..6a37c3fd7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/MediaFormat.java @@ -0,0 +1,64 @@ +package org.schabi.newpipe; + +/** + * Created by Adam Howard on 08/11/15. + * + * Copyright (c) Christian Schabesberger + * and Adam Howard 2015 + * + * VideoListAdapter.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +public enum MediaFormat { + // id name suffix mime type + MPEG_4 (0x0, "MPEG-4", "mp4", "video/mp4"), + v3GPP (0x1, "3GPP", "3gp", "video/3gpp"), + WEBM (0x2, "WebM", "webm", "video/webm"), + M4A (0x3, "m4a", "m4a", "audio/mp4"), + WEBMA (0x4, "WebM", "webm", "audio/webm"); + + public final int id; + public final String name; + public final String suffix; + public final String mimeType; + + MediaFormat(int id, String name, String suffix, String mimeType) { + this.id = id; + this.name = name; + this.suffix = suffix; + this.mimeType = mimeType; + } + + public static String getNameById(int ident) { + for (MediaFormat vf : MediaFormat.values()) { + if(vf.id == ident) return vf.name; + } + return ""; + } + + public static String getSuffixById(int ident) { + for (MediaFormat vf : MediaFormat.values()) { + if(vf.id == ident) return vf.suffix; + } + return ""; + } + + public static String getMimeById(int ident) { + for (MediaFormat vf : MediaFormat.values()) { + if(vf.id == ident) return vf.mimeType; + } + return ""; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java b/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java index 81a8aec33..d05f721a2 100644 --- a/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java +++ b/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java @@ -253,10 +253,9 @@ public class PlayVideoActivity extends AppCompatActivity { | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - } else { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); } + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); } private void adjustMediaControlMetrics() { diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfo.java b/app/src/main/java/org/schabi/newpipe/VideoInfo.java index 0e0e149dc..2b8dfc8e9 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfo.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfo.java @@ -1,5 +1,11 @@ package org.schabi.newpipe; +import android.graphics.Bitmap; +import android.util.Log; + +import java.util.Date; +import java.util.Vector; + /** * Created by Christian Schabesberger on 26.08.15. * @@ -20,82 +26,36 @@ package org.schabi.newpipe; * along with NewPipe. If not, see . */ -import android.graphics.Bitmap; -import android.util.Log; - -import java.util.Vector; public class VideoInfo { + public String id = ""; + public String title = ""; + public String uploader = ""; + public String thumbnail_url = ""; + public Bitmap thumbnail = null; + public String webpage_url = ""; + public String upload_date = ""; + public long view_count = 0; + + public String uploader_thumbnail_url = ""; + public Bitmap uploader_thumbnail = null; + public String description = ""; + public int duration = -1; + public int age_limit = 0; + public int like_count = 0; + public int dislike_count = 0; + public String average_rating = ""; + public VideoStream[] videoStreams = null; + public AudioStream[] audioStreams = null; + public VideoInfoItem nextVideo = null; + public VideoInfoItem[] relatedVideos = null; + public int videoAvailableStatus = VIDEO_AVAILABLE; private static final String TAG = VideoInfo.class.toString(); - // format identifier - public static final int I_MPEG_4 = 0x0; - public static final int I_3GPP = 0x1; - public static final int I_WEBM = 0x2; - public static final int I_M4A = 0x3; - public static final int I_WEBMA = 0x4; - - // format name - public static final String F_MPEG_4 = "MPEG-4"; - public static final String F_3GPP = "3GPP"; - public static final String F_WEBM = "WebM"; - public static final String F_M4A = "m4a"; - public static final String F_WEBMA = "WebM"; - - // file suffix - public static final String C_MPEG_4 = "mp4"; - public static final String C_3GPP = "3gp"; - public static final String C_WEBM = "webm"; - public static final String C_M4A = "m4a"; - public static final String C_WEBMA = "webm"; - - // mimeType - public static final String M_MPEG_4 = "video/mp4"; - public static final String M_3GPP = "video/3gpp"; - public static final String M_WEBM = "video/webm"; - public static final String M_M4A = "audio/mp4"; - public static final String M_WEBMA = "audio/webm"; - public static final int VIDEO_AVAILABLE = 0x00; public static final int VIDEO_UNAVAILABLE = 0x01; - public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation; sound pretty draconian - - public static String getNameById(int id) { - switch(id) { - case I_MPEG_4: return F_MPEG_4; - case I_3GPP: return F_3GPP; - case I_WEBM: return F_WEBM; - case I_M4A: return F_M4A; - case I_WEBMA: return F_WEBMA; - default: formatNotKnown(id); - } - return ""; - } - - public static String getSuffixById(int id) { - switch(id) { - case I_MPEG_4: return C_MPEG_4; - case I_3GPP: return C_3GPP; - case I_WEBM: return C_WEBM; - case I_M4A: return C_M4A; - case I_WEBMA: return C_WEBMA; - default: formatNotKnown(id); - } - return ""; - } - - public static String getMimeById(int id) { - switch(id) { - case I_MPEG_4: return M_MPEG_4; - case I_3GPP: return M_3GPP; - case I_WEBM: return M_WEBM; - case I_M4A: return M_M4A; - case I_WEBMA: return M_WEBMA; - default: formatNotKnown(id); - } - return ""; - } + public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation public static class VideoStream { public VideoStream(String url, int format, String res) { @@ -106,11 +66,6 @@ public class VideoInfo { public String resolution = ""; } - protected static void formatNotKnown(int id) { - Log.e(TAG, "format not known: \"" + - Integer.toString(id) + "\". Call the programmers, they messed it up!"); - } - public static class AudioStream { public AudioStream(String url, int format, int bandwidth, int samplingRate) { this.url = url; this.format = format; @@ -120,28 +75,5 @@ public class VideoInfo { public int format = -1; public int bandwidth = -1; public int samplingRate = -1; - } - - public String id = ""; - public String uploader = ""; - public String upload_date = ""; - public String uploader_thumbnail_url = ""; - public Bitmap uploader_thumbnail = null; - public String title = ""; - public String thumbnail_url = ""; - public Bitmap thumbnail = null; - public String description = ""; - public int duration = -1; - public int age_limit = 0; - public String webpage_url = ""; - public String view_count = ""; - public String like_count = ""; - public String dislike_count = ""; - public String average_rating = ""; - public VideoStream[] videoStreams = null; - public AudioStream[] audioStreams = null; - public VideoInfoItem nextVideo = null; - public VideoInfoItem[] relatedVideos = null; - public int videoAvailableStatus = VIDEO_AVAILABLE; } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItem.java b/app/src/main/java/org/schabi/newpipe/VideoInfoItem.java index e34f064b1..dca5f0edc 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfoItem.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfoItem.java @@ -28,13 +28,14 @@ public class VideoInfoItem implements Parcelable { public String id = ""; public String title = ""; public String uploader = ""; - public String duration = ""; public String thumbnail_url = ""; public Bitmap thumbnail = null; public String webpage_url = ""; public String upload_date = ""; public String view_count = ""; + public String duration = ""; + protected VideoInfoItem(Parcel in) { id = in.readString(); title = in.readString(); diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java index e6618ddbf..d15d6b86a 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java @@ -51,7 +51,7 @@ public class VideoInfoItemViewCreator { } if(info.thumbnail == null) { - holder.itemThumbnailView.setImageResource(R.drawable.dummi_thumbnail); + holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail); } else { holder.itemThumbnailView.setImageBitmap(info.thumbnail); } diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java index 370ac2fb2..2ba09c189 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.NavUtils; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -60,6 +61,7 @@ public class VideoItemDetailActivity extends AppCompatActivity { // this means the video was called though another app if (getIntent().getData() != null) { videoUrl = getIntent().getData().toString(); + Log.i(TAG, "video URL passed:\"" + videoUrl + "\""); StreamingService[] serviceList = ServiceList.getServices(); Extractor extractor = null; for (int i = 0; i < serviceList.length; i++) { @@ -67,8 +69,7 @@ public class VideoItemDetailActivity extends AppCompatActivity { arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i); try { currentStreamingService = i; - extractor = ServiceList.getService(i) - .getExtractorInstance(); + extractor = ServiceList.getService(i).getExtractorInstance(); } catch (Exception e) { e.printStackTrace(); } diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java index aab57e23f..e27d02278 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java @@ -2,6 +2,7 @@ package org.schabi.newpipe; import android.app.Activity; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; @@ -29,6 +30,11 @@ import android.widget.TextView; import android.view.MenuItem; import java.net.URL; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; import java.util.Vector; @@ -216,12 +222,31 @@ public class VideoItemDetailFragment extends Fragment { case VideoInfo.VIDEO_AVAILABLE: { videoTitleView.setText(info.title); uploaderView.setText(info.uploader); - viewCountView.setText(info.view_count + + Locale locale = getPreferredLocale(); + NumberFormat nf = NumberFormat.getInstance(locale); + String localisedViewCount = nf.format(info.view_count); + viewCountView.setText(localisedViewCount + " " + activity.getString(R.string.viewSufix)); - thumbsUpView.setText(info.like_count); - thumbsDownView.setText(info.dislike_count); + + + thumbsUpView.setText(nf.format(info.like_count)); + thumbsDownView.setText(nf.format(info.dislike_count)); + + //this is horribly convoluted + //TODO: find a better way to convert YYYY-MM-DD to a locale-specific date + //suggestions welcome + int year = Integer.parseInt(info.upload_date.substring(0, 4)); + int month = Integer.parseInt(info.upload_date.substring(5, 7)); + int date = Integer.parseInt(info.upload_date.substring(8, 10)); + Calendar cal = Calendar.getInstance(); + cal.set(year, month, date); + Date datum = cal.getTime(); + DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + + String localisedDate = df.format(datum); uploadDateView.setText( - activity.getString(R.string.uploadDatePrefix) + " " + info.upload_date); + activity.getString(R.string.uploadDatePrefix) + " " + localisedDate); descriptionView.setText(Html.fromHtml(info.description)); descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); @@ -369,6 +394,23 @@ public class VideoItemDetailFragment extends Fragment { } } + public Locale getPreferredLocale() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + String languageKey = getContext().getString(R.string.searchLanguage); + String languageCode = "en";//i know the following lines defaults languageCode to "en", but java is picky about uninitialised values + languageCode = sp.getString(languageKey, "en"); + + if(languageCode.length() == 2) { + return new Locale(languageCode); + } + else if(languageCode.contains("_")) { + String country = languageCode + .substring(languageCode.indexOf("_"), languageCode.length()); + return new Locale(languageCode.substring(0, 2), country); + } + return Locale.getDefault(); + } + public boolean checkIfLandscape() { DisplayMetrics displayMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java index ffb1c9697..856497649 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java @@ -103,7 +103,7 @@ public class VideoItemListActivity extends AppCompatActivity super.onCreate(savedInstanceState); setContentView(R.layout.activity_videoitem_list); - //-------- remove this line when multiservice support is implemented ---------- + //------ todo: remove this line when multiservice support is implemented ------ currentStreamingServiceId = ServiceList.getIdOfService("Youtube"); //----------------------------------------------------------------------------- diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java index 8de38c102..c9f6c0cd7 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java @@ -93,10 +93,10 @@ public class VideoItemListFragment extends ListFragment { public void run() { try { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); - String contentCountryKey = getContext().getString(R.string.contentCountry); - String contentCountry = sp.getString(contentCountryKey, ""); - SearchEngine.Result result = engine.search(query, page, contentCountry); - Log.i(TAG, "countryCode passed:\""+contentCountry+"\""); + String searchLanguageKey = getContext().getString(R.string.searchLanguage); + String searchLanguage = sp.getString(searchLanguageKey, "en"); + SearchEngine.Result result = engine.search(query, page, searchLanguage); + Log.i(TAG, "language code passed:\""+searchLanguage+"\""); if(run) { h.post(new ResultRunnable(result, requestId)); } diff --git a/app/src/main/java/org/schabi/newpipe/youtube/YoutubeExtractor.java b/app/src/main/java/org/schabi/newpipe/youtube/YoutubeExtractor.java index 8ba344361..6d23ae038 100644 --- a/app/src/main/java/org/schabi/newpipe/youtube/YoutubeExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/youtube/YoutubeExtractor.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.youtube; import android.util.Log; import android.util.Xml; -import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -14,12 +13,14 @@ import org.mozilla.javascript.Function; import org.mozilla.javascript.ScriptableObject; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.Extractor; +import org.schabi.newpipe.MediaFormat; import org.schabi.newpipe.VideoInfo; import org.schabi.newpipe.VideoInfoItem; import org.xmlpull.v1.XmlPullParser; import java.io.StringReader; import java.net.URI; +import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; import java.util.Vector; @@ -57,16 +58,16 @@ public class YoutubeExtractor implements Extractor { public static int resolveFormat(int itag) { switch(itag) { // video - case 17: return VideoInfo.I_3GPP; - case 18: return VideoInfo.I_MPEG_4; - case 22: return VideoInfo.I_MPEG_4; - case 36: return VideoInfo.I_3GPP; - case 37: return VideoInfo.I_MPEG_4; - case 38: return VideoInfo.I_MPEG_4; - case 43: return VideoInfo.I_WEBM; - case 44: return VideoInfo.I_WEBM; - case 45: return VideoInfo.I_WEBM; - case 46: return VideoInfo.I_WEBM; + case 17: return MediaFormat.v3GPP.id; + case 18: return MediaFormat.MPEG_4.id; + case 22: return MediaFormat.MPEG_4.id; + case 36: return MediaFormat.v3GPP.id; + case 37: return MediaFormat.MPEG_4.id; + case 38: return MediaFormat.MPEG_4.id; + case 43: return MediaFormat.WEBM.id; + case 44: return MediaFormat.WEBM.id; + case 45: return MediaFormat.WEBM.id; + case 46: return MediaFormat.WEBM.id; default: //Log.i(TAG, "Itag " + Integer.toString(itag) + " not known or not supported."); return -1; @@ -113,7 +114,7 @@ public class YoutubeExtractor implements Extractor { JSONObject jsonObj = new JSONObject(jsonString); //---------------------------------- - // load an parse description code + // load and parse description code //---------------------------------- if (decryptionCode.isEmpty()) { JSONObject ytAssets = jsonObj.getJSONObject("assets"); @@ -132,38 +133,27 @@ public class YoutubeExtractor implements Extractor { @Override public String getVideoId(String videoUrl) { - try { - URI uri = new URI(videoUrl); - if(uri.getHost().contains("youtube")) { - String query = uri.getFragment(); - if(query == null) { - query = uri.getQuery(); - } else { - query = query.replace("/watch?", ""); - } - String queryElements[] = query.split("&"); - Map queryArguments = new HashMap<>(); - for (String e : queryElements) { - String[] s = e.split("="); - queryArguments.put(s[0], s[1]); - } - return queryArguments.get("v"); - } else if(uri.getHost().contains("youtu.be")) { - // uri.getRawPath() does somehow not return the last character. - // so we do a workaround instead. - //return uri.getRawPath(); - String url[] = videoUrl.split("/"); - return url[url.length-1]; - } else { - Log.e(TAG, "Error could not parse url: " + videoUrl); + String id = ""; + Pattern pat; - } - } catch(Exception e) { + if(videoUrl.contains("youtube")) { + pat = Pattern.compile("youtube\\.com/watch\\?v=([\\-a-zA-Z0-9_]{11})"); + } + else if(videoUrl.contains("youtu.be")) { + pat = Pattern.compile("youtu\\.be/([a-zA-Z0-9_-]{11})"); + } + else { Log.e(TAG, "Error could not parse url: " + videoUrl); - e.printStackTrace(); return ""; } - return null; + Matcher mat = pat.matcher(videoUrl); + boolean foundMatch = mat.find(); + if(foundMatch){ + id = mat.group(1); + Log.i(TAG, "string \""+videoUrl+"\" matches!"); + } + Log.i(TAG, "string \""+videoUrl+"\" does not match."); + return id; } @Override @@ -178,12 +168,11 @@ public class YoutubeExtractor implements Extractor { Document doc = Jsoup.parse(site, siteUrl); - videoInfo.id = matchGroup1("v=([0-9a-zA-Z]*)", siteUrl); + videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", siteUrl); videoInfo.age_limit = 0; videoInfo.webpage_url = siteUrl; - initService(site); //------------------------------------- @@ -224,7 +213,7 @@ public class YoutubeExtractor implements Extractor { videoInfo.uploader = playerArgs.getString("author"); videoInfo.title = playerArgs.getString("title"); - //first attempt gating a small image version + //first attempt getting a small image version //in the html extracting part we try to get a thumbnail with a higher resolution videoInfo.thumbnail_url = playerArgs.getString("thumbnail_url"); videoInfo.duration = playerArgs.getInt("length_seconds"); @@ -243,7 +232,7 @@ public class YoutubeExtractor implements Extractor { } int itag = Integer.parseInt(tags.get("itag")); - String streamUrl = terrible_unescape_workaround_fuck(tags.get("url")); + String streamUrl = URLDecoder.decode(tags.get("url"), "UTF-8"); // if video has a signature: decrypt it and add it to the url if(tags.get("s") != null) { @@ -281,35 +270,37 @@ public class YoutubeExtractor implements Extractor { videoInfo.thumbnail_url = doc.select("link[itemprop=\"thumbnailUrl\"]").first() .attr("abs:href"); } catch(Exception e) { - Log.i(TAG, "Could not find high res Thumbnail. Use low res instead"); + Log.i(TAG, "Could not find high res Thumbnail. Using low res instead"); } // upload date - videoInfo.upload_date = doc.select("strong[class=\"watch-time-text\"").first() - .text(); + videoInfo.upload_date = doc.select("meta[itemprop=datePublished]").attr("content"); + + //TODO: Format date locale-specifically - // Try to only use date not the text around it - videoInfo.upload_date = matchGroup1("([0-9.]*$)", videoInfo.upload_date); // description - videoInfo.description = doc.select("p[id=\"eow-description\"]").first() - .html(); - + videoInfo.description = doc.select("p[id=\"eow-description\"]").first().html(); + String likesString = ""; + String dislikesString = ""; try { // likes - videoInfo.like_count = doc.select("span[class=\"like-button-renderer \"]").first() - .getAllElements().select("button") - .select("span").get(0).text(); - - + likesString = doc.select("button.like-button-renderer-like-button").first() + .select("span.yt-uix-button-content").first().text(); + videoInfo.like_count = Integer.parseInt(likesString.replaceAll("[^\\d]", "")); // dislikes - videoInfo.dislike_count = doc.select("span[class=\"like-button-renderer \"]").first() - .getAllElements().select("button") - .select("span").get(2).text(); + dislikesString = doc.select("button.like-button-renderer-dislike-button").first() + .select("span.yt-uix-button-content").first().text(); + + videoInfo.dislike_count = Integer.parseInt(dislikesString.replaceAll("[^\\d]", "")); + } catch(NumberFormatException nfe) { + Log.e(TAG, "failed to parse likesString \""+likesString+"\" and dislikesString \""+ + dislikesString+"\" as integers"); } catch(Exception e) { // if it fails we know that the video does not offer dislikes. - videoInfo.like_count = "0"; - videoInfo.dislike_count = "0"; + e.printStackTrace(); + videoInfo.like_count = 0; + videoInfo.dislike_count = 0; } // uploader thumbnail @@ -317,21 +308,20 @@ public class YoutubeExtractor implements Extractor { .select("img").first() .attr("abs:data-thumb"); - // view count - videoInfo.view_count = doc.select("div[class=\"watch-view-count\"]").first().text(); + // view count TODO: locale-specific formatting + String viewCountString = doc.select("meta[itemprop=interactionCount]").attr("content"); + videoInfo.view_count = Integer.parseInt(viewCountString); // next video videoInfo.nextVideo = extractVideoInfoItem(doc.select("div[class=\"watch-sidebar-section\"]").first() .select("li").first()); - int i = 0; // related videos Vector relatedVideos = new Vector<>(); for(Element li : doc.select("ul[id=\"watch-related\"]").first().children()) { // first check if we have a playlist. If so leave them out if(li.select("a[class*=\"content-link\"]").first() != null) { relatedVideos.add(extractVideoInfoItem(li)); - i++; } } videoInfo.relatedVideos = relatedVideos.toArray(new VideoInfoItem[relatedVideos.size()]); @@ -377,13 +367,13 @@ public class YoutubeExtractor implements Extractor { if(currentTagIsBaseUrl && (currentMimeType.contains("audio"))) { int format = -1; - if(currentMimeType.equals(VideoInfo.M_WEBMA)) { - format = VideoInfo.I_WEBMA; - } else if(currentMimeType.equals(VideoInfo.M_M4A)) { - format = VideoInfo.I_M4A; + if(currentMimeType.equals(MediaFormat.WEBMA.mimeType)) { + format = MediaFormat.WEBMA.id; + } else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) { + format = MediaFormat.M4A.id; } audioStreams.add(new VideoInfo.AudioStream(parser.getText(), - format, currentBandwidth, currentSamplingRate)); + format, currentBandwidth, currentSamplingRate)); } case XmlPullParser.END_TAG: if(tagName.equals("AdaptationSet")) { @@ -412,6 +402,7 @@ public class YoutubeExtractor implements Extractor { e.printStackTrace(); } + //todo: check NullPointerException causing info.title = li.select("span[class=\"title\"]").first().text(); info.view_count = li.select("span[class*=\"view-count\"]").first().text(); info.uploader = li.select("span[class=\"g-hovercard\"]").first().text(); @@ -431,19 +422,6 @@ public class YoutubeExtractor implements Extractor { return info; } - private String terrible_unescape_workaround_fuck(String shit) { - String[] splitAtEscape = shit.split("%"); - String retval = ""; - retval += splitAtEscape[0]; - for(int i = 1; i < splitAtEscape.length; i++) { - String escNum = splitAtEscape[i].substring(0, 2); - char c = (char) Integer.parseInt(escNum,16); - retval += c; - retval += splitAtEscape[i].substring(2); - } - return retval; - } - private String loadDecryptionCode(String playerUrl) { String playerCode = Downloader.download(playerUrl); String decryptionFuncName = ""; @@ -456,12 +434,13 @@ public class YoutubeExtractor implements Extractor { try { decryptionFuncName = matchGroup1("\\.sig\\|\\|([a-zA-Z0-9$]+)\\(", playerCode); - String functionPattern = "(function " + decryptionFuncName.replace("$", "\\$") + "\\([a-zA-Z0-9_]*\\)\\{.+?\\})"; + String functionPattern = "(var "+ decryptionFuncName.replace("$", "\\$") +"=function\\([a-zA-Z0-9_]*\\)\\{.+?\\})"; decryptionFunc = matchGroup1(functionPattern, playerCode); + decryptionFunc += ";"; helperObjectName = matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", decryptionFunc); - String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)function"; + String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)"; helperObject = matchGroup1(helperPattern, playerCode); } catch (Exception e) { @@ -474,13 +453,13 @@ public class YoutubeExtractor implements Extractor { return decryptionCode; } - private String decryptSignature(String encryptedSig, String decryptoinCode) { + private String decryptSignature(String encryptedSig, String decryptionCode) { Context context = Context.enter(); context.setOptimizationLevel(-1); Object result = null; try { ScriptableObject scope = context.initStandardObjects(); - context.evaluateString(scope, decryptoinCode, "decryptionCode", 1, null); + context.evaluateString(scope, decryptionCode, "decryptionCode", 1, null); Function decryptionFunc = (Function) scope.get("decrypt", scope); result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig}); } catch (Exception e) { @@ -498,7 +477,7 @@ public class YoutubeExtractor implements Extractor { return mat.group(1); } else { - Log.e(TAG, "failed to find pattern \""+pattern+"\""); + Log.e(TAG, "failed to find pattern \""+pattern+"\" inside of \""+input+"\""); new Exception("failed to find pattern \""+pattern+"\"").printStackTrace(); return ""; } diff --git a/app/src/main/java/org/schabi/newpipe/youtube/YoutubeSearchEngine.java b/app/src/main/java/org/schabi/newpipe/youtube/YoutubeSearchEngine.java index b2b1beeac..18bbf4337 100644 --- a/app/src/main/java/org/schabi/newpipe/youtube/YoutubeSearchEngine.java +++ b/app/src/main/java/org/schabi/newpipe/youtube/YoutubeSearchEngine.java @@ -49,7 +49,7 @@ public class YoutubeSearchEngine implements SearchEngine { private static final String TAG = YoutubeSearchEngine.class.toString(); @Override - public Result search(String query, int page, String countryCode) { + public Result search(String query, int page, String languageCode) { //String contentCountry = PreferenceManager.getDefaultSharedPreferences(this).getString(getString(R.string., ""); Uri.Builder builder = new Uri.Builder(); builder.scheme("https") @@ -59,20 +59,18 @@ public class YoutubeSearchEngine implements SearchEngine { .appendQueryParameter("page", Integer.toString(page)) .appendQueryParameter("filters", "video"); - //if we've been passed a valid, non-empty country code, append it to the URL - if(countryCode.length() > 0) { - if(countryCode.length() == 2) { - builder.appendQueryParameter("gl", countryCode); - builder.appendQueryParameter("persist_gl", "1"); - Log.i(TAG, "URI: \""+builder+"\""); - } - else { - Log.e(TAG, "invalid country code passed to search(): \""+countryCode+"\""); - } - } + String site; String url = builder.build().toString(); + //if we've been passed a valid language code, append it to the URL + if(languageCode.length() > 0) { + //assert Pattern.matches("[a-z]{2}(-([A-Z]{2}|[0-9]{1,3}))?", languageCode); + site = Downloader.download(url, languageCode); + } + else { + site = Downloader.download(url); + } + - String site = Downloader.download(url); Document doc = Jsoup.parse(site, url); Result result = new Result(); Element list = doc.select("ol[class=\"item-section\"]").first(); diff --git a/app/src/main/res/drawable/budy.png b/app/src/main/res/drawable/buddy.png similarity index 100% rename from app/src/main/res/drawable/budy.png rename to app/src/main/res/drawable/buddy.png diff --git a/app/src/main/res/drawable/dummi_thumbnail.png b/app/src/main/res/drawable/dummy_thumbnail.png similarity index 100% rename from app/src/main/res/drawable/dummi_thumbnail.png rename to app/src/main/res/drawable/dummy_thumbnail.png diff --git a/app/src/main/res/drawable/ic_file_download_black.png b/app/src/main/res/drawable/ic_file_download_black.png new file mode 100644 index 000000000..8c83bffa7 Binary files /dev/null and b/app/src/main/res/drawable/ic_file_download_black.png differ diff --git a/app/src/main/res/layout-land/fragment_videoitem_detail.xml b/app/src/main/res/layout-land/fragment_videoitem_detail.xml index 9e1d2885d..c0c4938d1 100644 --- a/app/src/main/res/layout-land/fragment_videoitem_detail.xml +++ b/app/src/main/res/layout-land/fragment_videoitem_detail.xml @@ -33,7 +33,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:adjustViewBounds="true" - android:src="@drawable/dummi_thumbnail"/> + android:src="@drawable/dummy_thumbnail"/> + android:src="@drawable/buddy" /> + android:src="@drawable/dummy_thumbnail"/> + android:src="@drawable/buddy" /> + android:src="@drawable/dummy_thumbnail"/> + android:text="Video title placeholder"/> + android:src="@drawable/buddy" /> + android:textStyle="bold" + android:textSize="@dimen/text_video_uploader_size" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="username" /> + android:text="81,754 views" /> + android:text="100" /> + android:text="20" /> + android:text="Published on Jan 01 1975" /> @@ -209,5 +219,5 @@ android:layout_height="wrap_content" app:backgroundTint="@color/primaryColorYoutube" android:src="@drawable/ic_play_arrow_black" - android:layout_margin="16dip"/> + android:layout_margin="20dp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/video_item.xml b/app/src/main/res/layout/video_item.xml index d25f524b6..e20e38ce2 100644 --- a/app/src/main/res/layout/video_item.xml +++ b/app/src/main/res/layout/video_item.xml @@ -12,7 +12,7 @@ android:scaleType="centerCrop" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" - android:src="@drawable/dummi_thumbnail"/> + android:src="@drawable/dummy_thumbnail"/> - + + - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b444b0725..93a3a82d1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,7 @@ - #dd0000 - #bb0000 + #cd322e + #bc211d #000000 #66000000 \ No newline at end of file diff --git a/app/src/main/res/values/dimentxt.xml b/app/src/main/res/values/dimentxt.xml index 916afd036..135a0e1cd 100644 --- a/app/src/main/res/values/dimentxt.xml +++ b/app/src/main/res/values/dimentxt.xml @@ -4,4 +4,10 @@ 11sp 12sp 12sp + 14sp + 14sp + 12sp + 14sp + 14sp + 14sp \ No newline at end of file diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index d427b7022..3c4b90e65 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -24,183 +24,163 @@ m4a show_next_video - content_country + search_language - - - US - DZ - AR - AU - AT - AZ - BH - BY - BE - BA - BR - BG - CA - CL - CO - HR - CZ - DK - EG - EE - FI - FR - GE - DE - GH - GR - HK - HU - IS - IN - ID - IE - IL - IT - JP - JO - KZ - KE - KW - LV - LB - LY - LT - LU - MK - MY - MX - ME - MA - NL - NZ - NG - NO - OM - PE - PH - PL - PT - PR - QA - RO - RU - SA - SN - RS - SG - SK - SI - ZA - KR - ES - SE - CH - TW - TZ - TH - TN - TR - UG - UA - AE - GB - VN - YE - ZW + + af + az + id + ms + ca + cs + da + de + et + en-GB + en + es + es-419 + eu + fil + fr + fr-CA + gl + hr + zu + is + it + sw + lv + lt + hu + nl + no + uz + pl + pt-PT + pt + ro + sq + sk + sl + fi + sv + vi + tr + bg + ky + kk + mk + mn + ru + sr + uk + el + hy + iw + ur + ar + fa + ne + mr + hi + bn + pa + gu + ta + te + kn + ml + si + th + lo + my + ka + am + km + zh-CN + zh-TW + zh-HK + ja + ko - - (None) - Worldwide (USA) - Algeria - Argentina - Australia - Austria - Azerbaijan - Bahrain - Belarus - Belgium - Bosnia and Herzegovina - Brazil - Bulgaria - Canada - Chile - Colombia - Croatia - Czech Republic - Denmark - Egypt - Estonia - Finland - France - Georgia - Germany - Ghana - Greece - Hong Kong - Hungary - Iceland - India - Indonesia - Ireland - Israel - Italy - Japan - Jordan - Kazakhstan - Kenya - Kuwait - Latvia - Lebanon - Libya - Lithuania - Luxembourg - Macedonia - Malaysia - Mexico - Montenegro - Morocco - Netherlands - New Zealand - Nigeria - Norway - Oman - Peru - Philippines - Poland - Portugal - Puerto Rico - Qatar - Romania - Russia - Saudi Arabia - Senegal - Serbia - Singapore - Slovakia - Slovenia - South Africa - South Korea - Spain - Sweden - Switzerland - Taiwan - Tanzania - Thailand - Tunisia - Turkey - Uganda - Ukraine - United Arab Emirates - United Kingdom - Vietnam - Yemen - Zimbabwe + + Afrikaans + Azərbaycan + Bahasa Indonesia + Bahasa Malaysia + Català + Čeština + Dansk + Deutsch + Eesti + English (UK) + English (US) + Español (España) + Español (Latinoamérica) + Euskara + Filipino + Français + Français (Canada) + Galego + Hrvatski + IsiZulu + Íslenska + Italiano + Kiswahili + Latviešu valoda + Lietuvių + Magyar + Nederlands + Norsk + O‘zbek + Polski + Português + Português (Brasil) + Română + Shqip + Slovenčina + Slovenščina + Suomi + Svenska + Tiếng Việt + Türkçe + Български + Кыргызча + Қазақ Тілі + Македонски + Монгол + Русский + Српски + Українська + Ελληνικά + Հայերեն + עברית + اردو + العربية + فارسی + नेपाली + मराठी + हिन्दी + বাংলা + ਪੰਜਾਬੀ + ગુજરાતી + தமிழ் + తెలుగు + ಕನ್ನಡ + മലയാളം + සිංහල + ภาษาไทย + ລາວ + ဗမာ + ქართული + አማርኛ + ខ្មែរ + 中文 (简体) + 中文 (繁體) + 中文 (香港) + 日本語 + 한국어 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 41f6b5530..a86ea5544 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,7 +4,7 @@ NewPipe Nothing found views - Uploaded at: + Published on No StreamPlayer found. You may want to install one. Install one Cancel @@ -45,9 +45,9 @@ Video Audio - Next Video - Show next and similar Videos. - Url not Supported. - Similar Videos - Video Content Country + Next video + Show next and similar videos + URL not supported. + Similar videos + Preferable content language \ No newline at end of file diff --git a/app/src/main/res/xml/settings_screen.xml b/app/src/main/res/xml/settings_screen.xml index 180b226a3..a72dc87df 100644 --- a/app/src/main/res/xml/settings_screen.xml +++ b/app/src/main/res/xml/settings_screen.xml @@ -51,12 +51,11 @@ android:title="@string/showNextAndSimilarTitle" android:defaultValue="true" /> - - --> + android:key="@string/searchLanguage" + android:title="@string/searchLanguageTitle" + android:entries="@array/languageNames" + android:entryValues="@array/languageCodes" + android:defaultValue="en" /> + \ No newline at end of file