Merge pull request #72 from medavox/master

Code cleanup & minor additions
This commit is contained in:
Christian Schabesberger 2015-11-03 17:54:09 +01:00
commit c51a5a51f1
14 changed files with 86 additions and 106 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
.gitignore
.gradle .gradle
/local.properties /local.properties
/.idea/workspace.xml /.idea/workspace.xml
@ -5,3 +6,5 @@
.DS_Store .DS_Store
/build /build
/captures /captures
.idea/gradle.xml
.idea/misc.xml

2
app/.gitignore vendored
View file

@ -1 +1,3 @@
.gitignore
/build /build
app/app.iml

View file

@ -68,6 +68,7 @@
<activity android:name=".PlayVideoActivity" <activity android:name=".PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullscreenTheme" android:theme="@style/FullscreenTheme"
android:parentActivityName=".VideoItemDetailActivity"
> >
</activity> </activity>
<activity <activity

View file

@ -4,6 +4,7 @@ import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.UnknownHostException;
/** /**
* Created by Christian Schabesberger on 14.08.15. * Created by Christian Schabesberger on 14.08.15.
@ -45,7 +46,13 @@ public class Downloader {
response.append(inputLine); response.append(inputLine);
} }
in.close(); in.close();
} catch (Exception e) {
}
catch(UnknownHostException uhe) {//thrown when there's no internet connection
uhe.printStackTrace();
//Toast.makeText(getActivity(), uhe.getMessage(), Toast.LENGTH_LONG).show();
}
catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return response.toString(); return response.toString();

View file

@ -25,11 +25,11 @@ public interface StreamingService {
public String name = ""; public String name = "";
} }
ServiceInfo getServiceInfo(); ServiceInfo getServiceInfo();
Class getExtractorClass(); Extractor getExtractorInstance();
Class getSearchEngineClass(); SearchEngine getSearchEngineInstance();
// When a VIEW_ACTION is caught this function will test if the url delivered within the calling /**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
// Intent was meant to be watched with this Service. Intent was meant to be watched with this Service.
// Return false if this service shall not allow to be callean through ACTIONs. Return false if this service shall not allow to be callean through ACTIONs.*/
boolean acceptUrl(String videoUrl); boolean acceptUrl(String videoUrl);
} }

View file

@ -59,7 +59,7 @@ public class VideoInfo {
public static final int VIDEO_AVAILABLE = 0x00; public static final int VIDEO_AVAILABLE = 0x00;
public static final int VIDEO_UNAVAILABLE = 0x01; public static final int VIDEO_UNAVAILABLE = 0x01;
public static final int VIDEO_UNAVAILABLE_GEMA = 0x02; public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation; sound pretty draconian
public static String getNameById(int id) { public static String getNameById(int id) {
switch(id) { switch(id) {
@ -68,8 +68,7 @@ public class VideoInfo {
case I_WEBM: return F_WEBM; case I_WEBM: return F_WEBM;
case I_M4A: return F_M4A; case I_M4A: return F_M4A;
case I_WEBMA: return F_WEBMA; case I_WEBMA: return F_WEBMA;
default: Log.e(TAG, "format not known: " + default: formatNotKnown(id);
Integer.toString(id) + "call the programmer he messed it up.");
} }
return ""; return "";
} }
@ -81,8 +80,7 @@ public class VideoInfo {
case I_WEBM: return C_WEBM; case I_WEBM: return C_WEBM;
case I_M4A: return C_M4A; case I_M4A: return C_M4A;
case I_WEBMA: return C_WEBMA; case I_WEBMA: return C_WEBMA;
default: Log.e(TAG, "format not known: " + default: formatNotKnown(id);
Integer.toString(id) + "call the programmer he messed it up.");
} }
return ""; return "";
} }
@ -94,8 +92,7 @@ public class VideoInfo {
case I_WEBM: return M_WEBM; case I_WEBM: return M_WEBM;
case I_M4A: return M_M4A; case I_M4A: return M_M4A;
case I_WEBMA: return M_WEBMA; case I_WEBMA: return M_WEBMA;
default: Log.e(TAG, "format not known: " + default: formatNotKnown(id);
Integer.toString(id) + "call the programmer he messed it up.");
} }
return ""; return "";
} }
@ -109,6 +106,11 @@ public class VideoInfo {
public String resolution = ""; 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 static class AudioStream {
public AudioStream(String url, int format, int bandwidth, int samplingRate) { public AudioStream(String url, int format, int bandwidth, int samplingRate) {
this.url = url; this.format = format; this.url = url; this.format = format;

View file

@ -61,7 +61,7 @@ public class VideoInfoItemViewCreator {
if(!info.upload_date.isEmpty()) { if(!info.upload_date.isEmpty()) {
holder.itemUploadDateView.setText(info.upload_date); holder.itemUploadDateView.setText(info.upload_date);
} else { } else {
//tewak if nececeary: This is a hack preventing to have a white space in the layout :P //tweak if necessary: This is a hack to prevent having white space in the layout :P
holder.itemUploadDateView.setText(info.view_count); holder.itemUploadDateView.setText(info.view_count);
} }

View file

@ -67,8 +67,8 @@ public class VideoItemDetailActivity extends AppCompatActivity {
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i); arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i);
try { try {
currentStreamingService = i; currentStreamingService = i;
extractor = (Extractor) ServiceList.getService(i) extractor = ServiceList.getService(i)
.getExtractorClass().newInstance(); .getExtractorInstance();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -122,9 +122,9 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// activity, the Up button is shown. Use NavUtils to allow users // activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For // to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design: // more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back // http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
Intent intent = new Intent(this, VideoItemListActivity.class); Intent intent = new Intent(this, VideoItemListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent); NavUtils.navigateUpTo(this, intent);

View file

@ -79,16 +79,16 @@ public class VideoItemDetailFragment extends Fragment {
private class ExtractorRunnable implements Runnable { private class ExtractorRunnable implements Runnable {
private Handler h = new Handler(); private Handler h = new Handler();
private Class extractorClass; private Extractor extractor;
private String videoUrl; private String videoUrl;
public ExtractorRunnable(String videoUrl, Class extractorClass) {
this.extractorClass = extractorClass; public ExtractorRunnable(String videoUrl, Extractor extractor, VideoItemDetailFragment f) {
this.extractor = extractor;
this.videoUrl = videoUrl; this.videoUrl = videoUrl;
} }
@Override @Override
public void run() { public void run() {
try { try {
Extractor extractor = (Extractor) extractorClass.newInstance();
VideoInfo videoInfo = extractor.getVideoInfo(videoUrl); VideoInfo videoInfo = extractor.getVideoInfo(videoUrl);
h.post(new VideoResultReturnedRunnable(videoInfo)); h.post(new VideoResultReturnedRunnable(videoInfo));
if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) { if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) {
@ -173,7 +173,7 @@ public class VideoItemDetailFragment extends Fragment {
} }
} catch (java.lang.NullPointerException e) { } catch (java.lang.NullPointerException e) {
// No god programm design i know. :/ // Not good program design, I know. :/
Log.w(TAG, "updateThumbnail(): Fragment closed before thread ended work"); Log.w(TAG, "updateThumbnail(): Fragment closed before thread ended work");
} }
} }
@ -324,7 +324,8 @@ public class VideoItemDetailFragment extends Fragment {
StreamingService streamingService = ServiceList.getService( StreamingService streamingService = ServiceList.getService(
getArguments().getInt(STREAMING_SERVICE)); getArguments().getInt(STREAMING_SERVICE));
extractorThread = new Thread(new ExtractorRunnable( extractorThread = new Thread(new ExtractorRunnable(
getArguments().getString(VIDEO_URL), streamingService.getExtractorClass())); getArguments().getString(VIDEO_URL), streamingService.getExtractorInstance(), this));
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY); autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
extractorThread.start(); extractorThread.start();
} catch (Exception e) { } catch (Exception e) {

View file

@ -106,6 +106,7 @@ public class VideoItemListActivity extends AppCompatActivity
//-------- remove this line when multiservice support is implemented ---------- //-------- remove this line when multiservice support is implemented ----------
currentStreamingServiceId = ServiceList.getIdOfService("Youtube"); currentStreamingServiceId = ServiceList.getIdOfService("Youtube");
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
listFragment = (VideoItemListFragment) getSupportFragmentManager() listFragment = (VideoItemListFragment) getSupportFragmentManager()
.findFragmentById(R.id.videoitem_list); .findFragmentById(R.id.videoitem_list);
listFragment.setStreamingService(ServiceList.getService(currentStreamingServiceId)); listFragment.setStreamingService(ServiceList.getService(currentStreamingServiceId));

View file

@ -72,14 +72,14 @@ public class VideoItemListFragment extends ListFragment {
} }
private class SearchRunnable implements Runnable { private class SearchRunnable implements Runnable {
private Class engineClass = null; private SearchEngine engine;
private String query; private String query;
private int page; private int page;
Handler h = new Handler(); Handler h = new Handler();
private volatile boolean run = true; private volatile boolean run = true;
private int requestId; private int requestId;
public SearchRunnable(Class engineClass, String query, int page, int requestId) { public SearchRunnable(SearchEngine engine, String query, int page, int requestId) {
this.engineClass = engineClass; this.engine = engine;
this.query = query; this.query = query;
this.page = page; this.page = page;
this.requestId = requestId; this.requestId = requestId;
@ -89,13 +89,6 @@ public class VideoItemListFragment extends ListFragment {
} }
@Override @Override
public void run() { public void run() {
SearchEngine engine = null;
try {
engine = (SearchEngine) engineClass.newInstance();
} catch(Exception e) {
e.printStackTrace();
return;
}
try { try {
SearchEngine.Result result = engine.search(query, page); SearchEngine.Result result = engine.search(query, page);
if(run) { if(run) {
@ -197,7 +190,8 @@ public class VideoItemListFragment extends ListFragment {
private void startSearch(String query, int page) { private void startSearch(String query, int page) {
currentRequestId++; currentRequestId++;
terminateThreads(); terminateThreads();
searchRunnable = new SearchRunnable(streamingService.getSearchEngineClass(), query, page, currentRequestId); searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(),
query, page, currentRequestId);
searchThread = new Thread(searchRunnable); searchThread = new Thread(searchRunnable);
searchThread.start(); searchThread.start();
} }
@ -252,10 +246,6 @@ public class VideoItemListFragment extends ListFragment {
} }
} }
void displayList() {
}
/** /**
* The serialization (saved instance state) Bundle key representing the * The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets. * activated item position. Only used on tablets.

View file

@ -3,6 +3,7 @@ package org.schabi.newpipe.youtube;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@ -50,7 +51,7 @@ public class YoutubeExtractor implements Extractor {
private static final String TAG = YoutubeExtractor.class.toString(); private static final String TAG = YoutubeExtractor.class.toString();
// These lists only contain itag formats that are supported by the common Android Video player. // These lists only contain itag formats that are supported by the common Android Video player.
// How ever if you are heading for a list showing all itag formats lock at // How ever if you are heading for a list showing all itag formats look at
// https://github.com/rg3/youtube-dl/issues/1687 // https://github.com/rg3/youtube-dl/issues/1687
public static int resolveFormat(int itag) { public static int resolveFormat(int itag) {
@ -141,14 +142,7 @@ public class YoutubeExtractor implements Extractor {
Document doc = Jsoup.parse(site, siteUrl); Document doc = Jsoup.parse(site, siteUrl);
try { videoInfo.id = matchGroup1("v=([0-9a-zA-Z]*)", siteUrl);
Pattern p = Pattern.compile("v=([0-9a-zA-Z]*)");
Matcher m = p.matcher(siteUrl);
m.find();
videoInfo.id = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
videoInfo.age_limit = 0; videoInfo.age_limit = 0;
videoInfo.webpage_url = siteUrl; videoInfo.webpage_url = siteUrl;
@ -160,16 +154,14 @@ public class YoutubeExtractor implements Extractor {
JSONObject ytAssets = null; JSONObject ytAssets = null;
String dashManifest; String dashManifest;
{ {
Pattern p = Pattern.compile("ytplayer.config\\s*=\\s*(\\{.*?\\});"); String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", site);
Matcher m = p.matcher(site);
m.find();
try { try {
playerArgs = (new JSONObject(m.group(1))) JSONObject jsonObj = new JSONObject(jsonString);
.getJSONObject("args"); playerArgs = jsonObj.getJSONObject("args");
ytAssets = (new JSONObject(m.group(1))) ytAssets = jsonObj.getJSONObject("assets");
.getJSONObject("assets"); }
}catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
// If we fail in this part the video is most likely not available. // If we fail in this part the video is most likely not available.
// Determining why is done later. // Determining why is done later.
@ -199,7 +191,7 @@ public class YoutubeExtractor implements Extractor {
videoInfo.audioStreams = parseDashManifest(dashManifest, decryptionCode); videoInfo.audioStreams = parseDashManifest(dashManifest, decryptionCode);
} catch (Exception e) { } catch (Exception e) {
//todo: check if the following statement is true //todo: check if the following statement is true
Log.e(TAG, "Dash manifest seems not to bee available."); Log.e(TAG, "Dash manifest doesn't seem to be available.");
e.printStackTrace(); e.printStackTrace();
} }
@ -263,15 +255,9 @@ public class YoutubeExtractor implements Extractor {
// upload date // upload date
videoInfo.upload_date = doc.select("strong[class=\"watch-time-text\"").first() videoInfo.upload_date = doc.select("strong[class=\"watch-time-text\"").first()
.text(); .text();
// Try to only use date not the text around it // Try to only use date not the text around it
try { videoInfo.upload_date = matchGroup1("([0-9.]*$)", videoInfo.upload_date);
Pattern p = Pattern.compile("([0-9.]*$)");
Matcher m = p.matcher(videoInfo.upload_date);
m.find();
videoInfo.upload_date = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
// description // description
videoInfo.description = doc.select("p[id=\"eow-description\"]").first() videoInfo.description = doc.select("p[id=\"eow-description\"]").first()
@ -322,16 +308,9 @@ public class YoutubeExtractor implements Extractor {
private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) { private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) {
if(!dashManifest.contains("/signature/")) { if(!dashManifest.contains("/signature/")) {
String encryptedSig = ""; String encryptedSig = matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifest);
String decryptedSig; String decryptedSig;
try {
Pattern p = Pattern.compile("/s/([a-fA-F0-9\\.]+)");
Matcher m = p.matcher(dashManifest);
m.find();
encryptedSig = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
decryptedSig = decryptSignature(encryptedSig, decryptoinCode); decryptedSig = decryptSignature(encryptedSig, decryptoinCode);
dashManifest = dashManifest.replace("/s/" + encryptedSig, "/signature/" + decryptedSig); dashManifest = dashManifest.replace("/s/" + encryptedSig, "/signature/" + decryptedSig);
} }
@ -396,10 +375,7 @@ public class YoutubeExtractor implements Extractor {
info.webpage_url = li.select("a[class*=\"content-link\"]").first() info.webpage_url = li.select("a[class*=\"content-link\"]").first()
.attr("abs:href"); .attr("abs:href");
try { try {
Pattern p = Pattern.compile("v=([0-9a-zA-Z-]*)"); info.id = matchGroup1("v=([0-9a-zA-Z-]*)", info.webpage_url);
Matcher m = p.matcher(info.webpage_url);
m.find();
info.id=m.group(1);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -446,27 +422,15 @@ public class YoutubeExtractor implements Extractor {
String decryptionCode; String decryptionCode;
try { try {
Pattern p = Pattern.compile("\\.sig\\|\\|([a-zA-Z0-9$]+)\\("); decryptionFuncName = matchGroup1("\\.sig\\|\\|([a-zA-Z0-9$]+)\\(", playerCode);
Matcher m = p.matcher(playerCode);
m.find();
decryptionFuncName = m.group(1);
String functionPattern = "(function " + decryptionFuncName.replace("$", "\\$") + "\\([a-zA-Z0-9_]*\\)\\{.+?\\})"; String functionPattern = "(function " + decryptionFuncName.replace("$", "\\$") + "\\([a-zA-Z0-9_]*\\)\\{.+?\\})";
p = Pattern.compile(functionPattern); decryptionFunc = matchGroup1(functionPattern, playerCode);
m = p.matcher(playerCode);
m.find();
decryptionFunc = m.group(1);
p = Pattern.compile(";([A-Za-z0-9_\\$]{2})\\...\\("); helperObjectName = matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", decryptionFunc);
m = p.matcher(decryptionFunc);
m.find();
helperObjectName = m.group(1);
String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)function"; String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)function";
p = Pattern.compile(helperPattern); helperObject = matchGroup1(helperPattern, playerCode);
m = p.matcher(playerCode);
m.find();
helperObject = m.group(1);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -493,4 +457,11 @@ public class YoutubeExtractor implements Extractor {
Context.exit(); Context.exit();
return result.toString(); return result.toString();
} }
private String matchGroup1(String pattern, String input) {
Pattern pat = Pattern.compile(pattern);
Matcher mat = pat.matcher(input);
mat.find();
return mat.group(1);
}
} }

View file

@ -57,14 +57,14 @@ public class YoutubeSearchEngine implements SearchEngine {
int i = 0; int i = 0;
for(Element item : list.children()) { for(Element item : list.children()) {
i++; i++;
/* First we need to determine witch kind of item we are working with. /* First we need to determine which kind of item we are working with.
Youtube depicts fife different kinds if items at its search result page. These are Youtube depicts five different kinds of items on its search result page. These are
regular videos, playlists, channels, two types of video suggestions, and a no video regular videos, playlists, channels, two types of video suggestions, and a "no video
found item. Since we only want videos, we net to filter out all the others. found" item. Since we only want videos, we need to filter out all the others.
An example for this can be seen here: An example for this can be seen here:
https://www.youtube.com/results?search_query=asdf&page=1 https://www.youtube.com/results?search_query=asdf&page=1
We already applied a filter to the url, so we don't need to care about channels, and We already applied a filter to the url, so we don't need to care about channels and
playlists now. playlists now.
*/ */
@ -102,9 +102,9 @@ public class YoutubeSearchEngine implements SearchEngine {
Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first() Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first()
.select("img").first(); .select("img").first();
resultItem.thumbnail_url = te.attr("abs:src"); resultItem.thumbnail_url = te.attr("abs:src");
// Sometimes youtube sends links to gif files witch somehow seam to not exist // Sometimes youtube sends links to gif files which somehow seem to not exist
// anymore. Items with such gif also offer a secondary image source. So we are going // anymore. Items with such gif also offer a secondary image source. So we are going
// to use that if we caught such an item. // to use that if we've caught such an item.
if(resultItem.thumbnail_url.contains(".gif")) { if(resultItem.thumbnail_url.contains(".gif")) {
resultItem.thumbnail_url = te.attr("abs:data-thumb"); resultItem.thumbnail_url = te.attr("abs:data-thumb");
} }
@ -113,7 +113,6 @@ public class YoutubeSearchEngine implements SearchEngine {
Log.e(TAG, "GREAT FUCKING ERROR"); Log.e(TAG, "GREAT FUCKING ERROR");
} }
} }
return result; return result;
} }
} }

View file

@ -1,6 +1,9 @@
package org.schabi.newpipe.youtube; package org.schabi.newpipe.youtube;
import org.schabi.newpipe.StreamingService; import org.schabi.newpipe.StreamingService;
import org.schabi.newpipe.Extractor;
import org.schabi.newpipe.SearchEngine;
/** /**
* Created by Christian Schabesberger on 23.08.15. * Created by Christian Schabesberger on 23.08.15.
@ -30,12 +33,12 @@ public class YoutubeService implements StreamingService {
return serviceInfo; return serviceInfo;
} }
@Override @Override
public Class getExtractorClass() { public Extractor getExtractorInstance() {
return YoutubeExtractor.class; return new YoutubeExtractor();
} }
@Override @Override
public Class getSearchEngineClass() { public SearchEngine getSearchEngineInstance() {
return YoutubeSearchEngine.class; return new YoutubeSearchEngine();
} }
@Override @Override
public boolean acceptUrl(String videoUrl) { public boolean acceptUrl(String videoUrl) {