diff --git a/sampleApp/src/main/AndroidManifest.xml b/sampleApp/src/main/AndroidManifest.xml index 070b78d..d5aeea0 100644 --- a/sampleApp/src/main/AndroidManifest.xml +++ b/sampleApp/src/main/AndroidManifest.xml @@ -18,6 +18,8 @@ + + diff --git a/sampleApp/src/main/java/at/huber/sampleDownload/SampleDownloadActivity.java b/sampleApp/src/main/java/at/huber/sampleDownload/SampleDownloadActivity.java index e28eb2c..ce96c99 100644 --- a/sampleApp/src/main/java/at/huber/sampleDownload/SampleDownloadActivity.java +++ b/sampleApp/src/main/java/at/huber/sampleDownload/SampleDownloadActivity.java @@ -21,7 +21,7 @@ public class SampleDownloadActivity extends Activity { - private static String youtubeLink; + private static String youtubeLink = "https://www.youtube.com/watch?v=DZrTFdUcWHY"; private LinearLayout mainLayout; private ProgressBar mainProgressBar; @@ -52,8 +52,9 @@ && getIntent().getType() != null && "text/plain".equals(getIntent().getType())) } else if (savedInstanceState != null && youtubeLink != null) { getYoutubeDownloadUrl(youtubeLink); } else { - finish(); + //finish(); } + getYoutubeDownloadUrl(youtubeLink); } private void getYoutubeDownloadUrl(String youtubeLink) { diff --git a/youtubeExtractor/build.gradle b/youtubeExtractor/build.gradle index d35602a..2295993 100644 --- a/youtubeExtractor/build.gradle +++ b/youtubeExtractor/build.gradle @@ -17,6 +17,7 @@ dependencies { exclude module: 'appcompat-v7' } implementation 'com.android.support:support-annotations:28.0.0' + implementation("com.squareup.okhttp3:okhttp:3.10.0") androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' diff --git a/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/OkHttpHelper.java b/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/OkHttpHelper.java new file mode 100644 index 0000000..519e7bf --- /dev/null +++ b/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/OkHttpHelper.java @@ -0,0 +1,38 @@ +package at.huber.youtubeExtractor; + +import android.support.annotation.NonNull; + +import java.io.IOException; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public abstract class OkHttpHelper { + + public static String makeRequest(@NonNull OkHttpClient client, @NonNull String url) throws IOException { + Response response = null; + try { + final Request request; + try { + request = new Request.Builder() + .url(url) + .get() + .build(); + } catch (Exception e) { + throw new IOException("Can't create request: " + e.getMessage()); + } + response = client.newCall(request).execute(); + if (!response.isSuccessful()) + throw new IOException("Response is not successful: " + response); + final ResponseBody body = response.body(); + if (body == null) + throw new IOException("Response body is null: " + response); + return body.string(); + } finally { + if (response != null) + response.close(); + } + } +} diff --git a/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/YouTubeExtractor.java b/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/YouTubeExtractor.java index 7b02398..810783f 100644 --- a/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/YouTubeExtractor.java +++ b/youtubeExtractor/src/main/java/at/huber/youtubeExtractor/YouTubeExtractor.java @@ -5,6 +5,8 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; @@ -20,7 +22,6 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.ref.WeakReference; -import java.net.HttpURLConnection; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; @@ -31,6 +32,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + public abstract class YouTubeExtractor extends AsyncTask> { private final static boolean CACHING = true; @@ -81,7 +87,7 @@ public abstract class YouTubeExtractor extends AsyncTask(con); cacheDirPath = con.getCacheDir().getAbsolutePath(); + + mClient = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + final Request original = chain.request(); + final Request header = original.newBuilder() + .addHeader("User-Agent", USER_AGENT) + .build(); + return chain.proceed(header); + } + }) + .build(); + } @Override @@ -171,6 +198,37 @@ public void extract(String youtubeLink, boolean parseDashManifest, boolean inclu this.execute(youtubeLink); } + @Nullable + public SparseArray extractSync(String youtubeLink, boolean parseDashManifest, boolean includeWebM) { + this.includeWebM = includeWebM; + videoID = null; + String ytUrl = youtubeLink; + if (ytUrl == null) { + return null; + } + Matcher mat = patYouTubePageLink.matcher(ytUrl); + if (mat.find()) { + videoID = mat.group(3); + } else { + mat = patYouTubeShortLink.matcher(ytUrl); + if (mat.find()) { + videoID = mat.group(3); + } else if (ytUrl.matches("\\p{Graph}+?")) { + videoID = ytUrl; + } + } + if (videoID != null) { + try { + return getStreamUrls(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + Log.e(LOG_TAG, "Wrong YouTube link format"); + } + return null; + } + protected abstract void onExtractionComplete(SparseArray ytFiles, VideoMeta videoMeta); @Override @@ -204,27 +262,15 @@ protected SparseArray doInBackground(String... params) { } private SparseArray getStreamUrls() throws IOException, InterruptedException { - String ytInfoUrl = (useHttp) ? "http://" : "https://"; ytInfoUrl += "www.youtube.com/get_video_info?video_id=" + videoID + "&eurl=" + URLEncoder.encode("https://youtube.googleapis.com/v/" + videoID, "UTF-8"); String streamMap; BufferedReader reader = null; - URL getUrl = new URL(ytInfoUrl); - if(LOGGING) + if (LOGGING) Log.d(LOG_TAG, "infoUrl: " + ytInfoUrl); - HttpURLConnection urlConnection = (HttpURLConnection) getUrl.openConnection(); - urlConnection.setRequestProperty("User-Agent", USER_AGENT); - try { - reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); - streamMap = reader.readLine(); - - } finally { - if (reader != null) - reader.close(); - urlConnection.disconnect(); - } + streamMap = OkHttpHelper.makeRequest(mClient, ytInfoUrl); Matcher mat; String curJsFileName; SparseArray encSignatures = null; @@ -234,31 +280,24 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep parseVideoMeta(streamMap); - if(videoMeta.isLiveStream()){ + if (videoMeta.isLiveStream()) { mat = patHlsvp.matcher(streamMap); - if(mat.find()) { + if (mat.find()) { String hlsvp = URLDecoder.decode(mat.group(1), "UTF-8"); SparseArray ytFiles = new SparseArray<>(); - getUrl = new URL(hlsvp); - urlConnection = (HttpURLConnection) getUrl.openConnection(); - urlConnection.setRequestProperty("User-Agent", USER_AGENT); - try { - reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - if(line.startsWith("https://") || line.startsWith("http://")){ - mat = patHlsItag.matcher(line); - if(mat.find()){ - int itag = Integer.parseInt(mat.group(1)); - YtFile newFile = new YtFile(FORMAT_MAP.get(itag), line); - ytFiles.put(itag, newFile); - } - } + String[] result = OkHttpHelper.makeRequest(mClient, hlsvp).split("\n"); + for (String line : result) { + if (TextUtils.isEmpty(line)) + continue; + if (line.startsWith("https://") || line.startsWith("http://")) { + mat = patHlsItag.matcher(line); + if (mat.find()) { + int itag = Integer.parseInt(mat.group(1)); + YtFile newFile = new YtFile(FORMAT_MAP.get(itag), line); + ytFiles.put(itag, newFile); + } } - } finally { - reader.close(); - urlConnection.disconnect(); } if (ytFiles.size() == 0) { @@ -274,7 +313,7 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep // "use_cipher_signature" disappeared, we check whether at least one ciphered signature // exists int the stream_map. boolean sigEnc = true, statusFail = false; - if(!patCipher.matcher(streamMap).find()) { + if (!patCipher.matcher(streamMap).find()) { sigEnc = false; if (!patStatusOk.matcher(streamMap).find()) { statusFail = true; @@ -292,24 +331,18 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep if (LOGGING) Log.d(LOG_TAG, "Get from youtube page"); - getUrl = new URL("https://youtube.com/watch?v=" + videoID); - urlConnection = (HttpURLConnection) getUrl.openConnection(); - urlConnection.setRequestProperty("User-Agent", USER_AGENT); - try { - reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - // Log.d("line", line); - mat = patYtPlayer.matcher(line); - if (mat.find()) { - streamMap = line.replace("\\\"", "\""); - break; - } + String[] result = OkHttpHelper.makeRequest( + mClient, "https://youtube.com/watch?v=" + videoID).split("\n"); + for (String line : result) { + if (TextUtils.isEmpty(line)) + continue; + mat = patYtPlayer.matcher(line); + if (mat.find()) { + streamMap = line.replace("\\\"", "\""); + break; } - } finally { - reader.close(); - urlConnection.disconnect(); } + encSignatures = new SparseArray<>(); mat = patDecryptionJsFile.matcher(streamMap); @@ -329,8 +362,7 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep if (sigEnc) { mat = patCipher.matcher(streamMap); - } - else { + } else { mat = patUrl.matcher(streamMap); } @@ -388,7 +420,7 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep if (encSignatures != null) { if (LOGGING) - Log.d(LOG_TAG, "Decipher signatures: " + encSignatures.size()+ ", videos: " + ytFiles.size()); + Log.d(LOG_TAG, "Decipher signatures: " + encSignatures.size() + ", videos: " + ytFiles.size()); String signature; decipheredSignature = null; if (decipherSignature(encSignatures)) { @@ -407,7 +439,7 @@ private SparseArray getStreamUrls() throws IOException, InterruptedExcep for (int i = 0; i < encSignatures.size() && i < sigs.length; i++) { int key = encSignatures.keyAt(i); String url = ytFiles.get(key).getUrl(); - url += "&sig=" + sigs[i]; + url += "&sig=" + sigs[i]; YtFile newFile = new YtFile(FORMAT_MAP.get(key), url); ytFiles.put(key, newFile); } @@ -427,25 +459,16 @@ private boolean decipherSignature(final SparseArray encSignatures) throw if (decipherFunctionName == null || decipherFunctions == null) { String decipherFunctUrl = "https://s.ytimg.com/yts/jsbin/" + decipherJsFileName; - BufferedReader reader = null; String javascriptFile; - URL url = new URL(decipherFunctUrl); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setRequestProperty("User-Agent", USER_AGENT); - try { - reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); - StringBuilder sb = new StringBuilder(""); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - sb.append(" "); - } - javascriptFile = sb.toString(); - } finally { - if (reader != null) - reader.close(); - urlConnection.disconnect(); + String[] result = OkHttpHelper.makeRequest(mClient, decipherFunctUrl).split("\n"); + StringBuilder sb = new StringBuilder(""); + for (String line : result) { + if (TextUtils.isEmpty(line)) + continue; + sb.append(line); + sb.append(" "); } + javascriptFile = sb.toString(); if (LOGGING) Log.d(LOG_TAG, "Decipher FunctURL: " + decipherFunctUrl); @@ -551,7 +574,7 @@ private void parseVideoMeta(String getVideoInfo) { } mat = patHlsvp.matcher(getVideoInfo); - if(mat.find()) + if (mat.find()) isLiveStream = true; mat = patAuthor.matcher(getVideoInfo); @@ -651,8 +674,7 @@ private void writeDeciperFunctToChache() { private void decipherViaWebView(final SparseArray encSignatures) { final Context context = refContext.get(); - if (context == null) - { + if (context == null) { return; } @@ -689,7 +711,7 @@ public void onResult(String result) { public void onError(String errorMessage) { lock.lock(); try { - if(LOGGING) + if (LOGGING) Log.e(LOG_TAG, errorMessage); jsExecuting.signal(); } finally {