在 Java、Android 上从 Youtube 下载视频的代码

Posted

技术标签:

【中文标题】在 Java、Android 上从 Youtube 下载视频的代码【英文标题】:Code for download video from Youtube on Java, Android 【发布时间】:2011-11-04 09:45:48 【问题描述】:

我创建了用于从 Youtube 下载视频的代码,但此代码不适用于 Wi-fi 连接和移动连接。我哪里错了?

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;

import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.MediaController;
import android.widget.VideoView;

public class MyActivity extends Activity 

    private class ReceivingDataFromYoutube extends AsyncTask<String, Void, Void> 

        private ProgressDialog dialog = new ProgressDialog(MyActivity.this);
        private String result;

        protected void onPreExecute() 
            dialog.setMessage("Downloading...");
            dialog.show();
        

        @Override
        protected Void doInBackground(String... arg0) 
            int begin, end;
            String tmpstr = null;
            try 
                URL url=new URL("http://www.youtube.com/watch?v=y12-1miZHLs&nomobile=1");
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("GET");
                InputStream stream=con.getInputStream();
                InputStreamReader reader=new InputStreamReader(stream);
                StringBuffer buffer=new StringBuffer();
                char[] buf=new char[262144];                
                int chars_read;
                while ((chars_read = reader.read(buf, 0, 262144)) != -1) 
                    buffer.append(buf, 0, chars_read);
                
                tmpstr=buffer.toString();

                begin  = tmpstr.indexOf("url_encoded_fmt_stream_map=");
                end = tmpstr.indexOf("&", begin + 27);
                if (end == -1) 
                    end = tmpstr.indexOf("\"", begin + 27);
                
                tmpstr = UtilClass.URLDecode(tmpstr.substring(begin + 27, end));

             catch (MalformedURLException e) 
                throw new RuntimeException();
             catch (IOException e) 
                throw new RuntimeException();
            

            Vector url_encoded_fmt_stream_map = new Vector();
            begin = 0;
            end   = tmpstr.indexOf(",");

            while (end != -1) 
                url_encoded_fmt_stream_map.addElement(tmpstr.substring(begin, end));
                begin = end + 1;
                end   = tmpstr.indexOf(",", begin);
            

            url_encoded_fmt_stream_map.addElement(tmpstr.substring(begin, tmpstr.length()));
            String result = "";
            Enumeration url_encoded_fmt_stream_map_enum = url_encoded_fmt_stream_map.elements();
            while (url_encoded_fmt_stream_map_enum.hasMoreElements()) 
                tmpstr = (String)url_encoded_fmt_stream_map_enum.nextElement();
                begin = tmpstr.indexOf("itag=");
                if (begin != -1) 
                    end = tmpstr.indexOf("&", begin + 5);

                    if (end == -1) 
                          end = tmpstr.length();
                    

                    int fmt = Integer.parseInt(tmpstr.substring(begin + 5, end));

                    if (fmt == 35) 
                        begin = tmpstr.indexOf("url=");
                        if (begin != -1) 
                            end = tmpstr.indexOf("&", begin + 4);
                            if (end == -1) 
                               end = tmpstr.length();
                            
                            result = UtilClass.URLDecode(tmpstr.substring(begin + 4, end));
                            this.result=result;
                            break;
                        
                    
                
                     
            try 
              URL u = new URL(result);
              HttpURLConnection c = (HttpURLConnection) u.openConnection();
              c.setRequestMethod("GET");
/*              c.setRequestProperty("Youtubedl-no-compression", "True");
              c.setRequestProperty("User-Agent", "YouTube");*/

              c.setDoOutput(true);
              c.connect();

              FileOutputStream f=new FileOutputStream(new File("/sdcard/3.flv"));

              InputStream in=c.getInputStream();
              byte[] buffer=new byte[1024];
              int sz = 0;
              while ( (sz = in.read(buffer)) > 0 ) 
                   f.write(buffer,0, sz);
              
              f.close();
             catch (MalformedURLException e) 
                new RuntimeException();
             catch (IOException e) 
                new RuntimeException();
            
            return null;
        

        protected void onPostExecute(Void unused) 
            dialog.dismiss();
            

    


    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new ReceivingDataFromYoutube().execute();
       
  

【问题讨论】:

您好,您能分享一下您是如何解决这个问题的吗?如何下载 youtube 视频? @Денис Нагибин 嘿,你能告诉我它是如何工作的,因为我现在想知道它是如何工作的......我只是复制你的代码,但它在“UtilClass”上给出错误。 这个 UtilClass.URLDecode() 在你的函数中是什么,你能解释一下.. 我不明白 老兄,你写了 UtilClass。你愿意分享这门课吗? 你能回答我的那个问题吗***.com/questions/26398325/… 【参考方案1】:

三个步骤:

    查看YouTube的源代码(html),你会得到这样的链接http%253A%252F%252Fo-o.preferred.telemar-cnf1.v18.lscache6.c.youtube.com%252Fvideoplayback ...

    使用代码解码url(删除代码%2B%25等)、create a decoder并使用函数Uri.decode(url)替换无效的转义八位字节

    使用代码下载流:

        URL u = null;
        InputStream is = null;  
        
        try 
            u = new URL(url);
            is = u.openStream(); 
            HttpURLConnection huc = (HttpURLConnection)u.openConnection(); //to know the size of video
            int size = huc.getContentLength();                 
 
            if(huc != null) 
                String fileName = "FILE.mp4";
                String storagePath = Environment.getExternalStorageDirectory().toString();
                File f = new File(storagePath,fileName);
               
                FileOutputStream fos = new FileOutputStream(f);
                byte[] buffer = new byte[1024];
                int len1 = 0;
                if(is != null) 
                    while ((len1 = is.read(buffer)) > 0) 
                        fos.write(buffer,0, len1);  
                    
                
                if(fos != null) 
                    fos.close();
                
                                   
         catch (MalformedURLException mue) 
            mue.printStackTrace();
         catch (IOException ioe) 
            ioe.printStackTrace();
         finally 
            try                
                if(is != null) 
                    is.close();
                
             catch (IOException ioe) 
                // just going to ignore this one
            
        

这就是您在网络上可以找到的大部分内容!!!

【讨论】:

真的为我工作@Денис Нагибин。我有一个私人应用程序,只能在我的手机上下载 youtube 视频。 此方法不再有效。你能看看它并更新它吗? @SilvioGuedes 嘿,这是可行的,因为我很容易下载文件,但是当我要播放它时,会显示错误“您无法播放视频”。 @SilvioGuedes 嘿,谢谢你的回复,但我在我的应用程序中谈论的是不是其他应用程序。所以这不是完美的答案。 它只是下载一个 108 KB 的文件,无法播放。 huc.getContentLength();总是返回 -1。【参考方案2】:

方法1 (推荐)

图书馆YouTubeExtractor

添加到你的 gradle 文件中

 allprojects 
        repositories 
            maven  url "https://jitpack.io" 
        
    

和依赖关系

 compile 'com.github.Commit451.YouTubeExtractor:youtubeextractor:2.1.0'

添加这个小代码,你就完成了。 Demo HERE

public class MainActivity extends AppCompatActivity 

    private static final String YOUTUBE_ID = "ea4-5mrpGfE";

    private final YouTubeExtractor mExtractor = YouTubeExtractor.create();


    private Callback<YouTubeExtractionResult> mExtractionCallback = new Callback<YouTubeExtractionResult>() 
        @Override
        public void onResponse(Call<YouTubeExtractionResult> call, Response<YouTubeExtractionResult> response) 
            bindVideoResult(response.body());
        

        @Override
        public void onFailure(Call<YouTubeExtractionResult> call, Throwable t) 
            onError(t);
        
    ;


    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


//        For android youtube extractor library  com.github.Commit451.YouTubeExtractor:youtubeextractor:2.1.0'
        mExtractor.extract(YOUTUBE_ID).enqueue(mExtractionCallback);

    


    private void onError(Throwable t) 
        t.printStackTrace();
        Toast.makeText(MainActivity.this, "It failed to extract. So sad", Toast.LENGTH_SHORT).show();
    


    private void bindVideoResult(YouTubeExtractionResult result) 

//        Here you can get download url link
        Log.d("OnSuccess", "Got a result with the best url: " + result.getBestAvailableQualityVideoUri());

        Toast.makeText(this, "result : " + result.getSd360VideoUri(), Toast.LENGTH_SHORT).show();
    

您可以在bindVideoResult()方法中获取下载链接。

方法 2

使用这个库android-youtubeExtractor

添加到 gradle 文件中

repositories 
    maven  url "https://jitpack.io" 


compile 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'

这里是获取下载地址的代码。

       String youtubeLink = "http://youtube.com/watch?v=xxxx";

    YouTubeUriExtractor ytEx = new YouTubeUriExtractor(this) 
        @Override
        public void onUrisAvailable(String videoId, String videoTitle, SparseArray<YtFile> ytFiles) 
            if (ytFiles != null) 
                int itag = 22;
// Here you can get download url
                String downloadUrl = ytFiles.get(itag).getUrl();
            
        
    ;

    ytEx.execute(youtubeLink);

【讨论】:

@试过1种方法?得到了结果.. 但是如何下载视频? 如果您有下载链接,那么您可以像下载 pdf 图片等一样下载视频。即使您可以使用下载管理器,这也很简单。 这是超级且 100% 工作的代码。我现在可以从 youtube 下载任何视频。谢谢兄弟,你真的很棒 我很高兴听到这个消息 @ArpitPatel 真的,如果我遇到你兄弟,我会欠你一顿午餐。你能告诉我如何下载 DailyMotion 视频吗?请帮助我,我正在制作视频下载器,我想从任何网址下载任何视频【参考方案3】:

参考:Youtube Video Download (Android/Java)

private static final HashMap<String, Meta> typeMap = new HashMap<String, Meta>();

initTypeMap();先打电话

class Meta 
    public String num;
    public String type;
    public String ext;

    Meta(String num, String ext, String type) 
        this.num = num;
        this.ext = ext;
        this.type = type;
    


class Video 
    public String ext = "";
    public String type = "";
    public String url = "";

    Video(String ext, String type, String url) 
        this.ext = ext;
        this.type = type;
        this.url = url;
    


public ArrayList<Video> getStreamingUrisFromYouTubePage(String ytUrl)
        throws IOException 
    if (ytUrl == null) 
        return null;
    

    // Remove any query params in query string after the watch?v=<vid> in
    // e.g.
    // http://www.youtube.com/watch?v=0RUPACpf8Vs&feature=youtube_gdata_player
    int andIdx = ytUrl.indexOf('&');
    if (andIdx >= 0) 
        ytUrl = ytUrl.substring(0, andIdx);
    

    // Get the HTML response
    /* String userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1)";*/
   /* HttpClient client = new DefaultHttpClient();
    client.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
            userAgent);
    HttpGet request = new HttpGet(ytUrl);
    HttpResponse response = client.execute(request);*/
    String html = "";
    HttpsURLConnection c = (HttpsURLConnection) new URL(ytUrl).openConnection();
    c.setRequestMethod("GET");
    c.setDoOutput(true);
    c.connect();
    InputStream in = c.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    StringBuilder str = new StringBuilder();
    String line = null;
    while ((line = reader.readLine()) != null) 
        str.append(line.replace("\\u0026", "&"));
    
    in.close();
    html = str.toString();

    // Parse the HTML response and extract the streaming URIs
    if (html.contains("verify-age-thumb")) 
        Log.e("Downloader", "YouTube is asking for age verification. We can't handle that sorry.");
        return null;
    

    if (html.contains("das_captcha")) 
        Log.e("Downloader", "Captcha found, please try with different IP address.");
        return null;
    

    Pattern p = Pattern.compile("stream_map\":\"(.*?)?\"");
    // Pattern p = Pattern.compile("/stream_map=(.[^&]*?)\"/");
    Matcher m = p.matcher(html);
    List<String> matches = new ArrayList<String>();
    while (m.find()) 
        matches.add(m.group());
    

    if (matches.size() != 1) 
        Log.e("Downloader", "Found zero or too many stream maps.");
        return null;
    

    String urls[] = matches.get(0).split(",");
    HashMap<String, String> foundArray = new HashMap<String, String>();
    for (String ppUrl : urls) 
        String url = URLDecoder.decode(ppUrl, "UTF-8");
        Log.e("URL","URL : "+url);

        Pattern p1 = Pattern.compile("itag=([0-9]+?)[&]");
        Matcher m1 = p1.matcher(url);
        String itag = null;
        if (m1.find()) 
            itag = m1.group(1);
        

        Pattern p2 = Pattern.compile("signature=(.*?)[&]");
        Matcher m2 = p2.matcher(url);
        String sig = null;
        if (m2.find()) 
            sig = m2.group(1);
         else 
            Pattern p23 = Pattern.compile("signature&s=(.*?)[&]");
            Matcher m23 = p23.matcher(url);
            if (m23.find()) 
                sig = m23.group(1);
            
        

        Pattern p3 = Pattern.compile("url=(.*?)[&]");
        Matcher m3 = p3.matcher(ppUrl);
        String um = null;
        if (m3.find()) 
            um = m3.group(1);
        

        if (itag != null && sig != null && um != null) 
            Log.e("foundArray","Adding Value");
            foundArray.put(itag, URLDecoder.decode(um, "UTF-8") + "&"
                    + "signature=" + sig);
        
    
    Log.e("foundArray","Size : "+foundArray.size());
    if (foundArray.size() == 0) 
        Log.e("Downloader", "Couldn't find any URLs and corresponding signatures");
        return null;
    


    ArrayList<Video> videos = new ArrayList<Video>();

    for (String format : typeMap.keySet()) 
        Meta meta = typeMap.get(format);

        if (foundArray.containsKey(format)) 
            Video newVideo = new Video(meta.ext, meta.type,
                    foundArray.get(format));
            videos.add(newVideo);
            Log.d("Downloader", "YouTube Video streaming details: ext:" + newVideo.ext
                    + ", type:" + newVideo.type + ", url:" + newVideo.url);
        
    

    return videos;


private class YouTubePageStreamUriGetter extends AsyncTask<String, String, ArrayList<Video>> 
    ProgressDialog progressDialog;

    @Override
    protected void onPreExecute() 
        super.onPreExecute();
        progressDialog = ProgressDialog.show(webViewActivity.this, "",
                "Connecting to YouTube...", true);
    

    @Override
    protected ArrayList<Video> doInBackground(String... params) 
        ArrayList<Video> fVideos = new ArrayList<>();
        String url = params[0];
        try 
            ArrayList<Video> videos = getStreamingUrisFromYouTubePage(url);
            /*                Log.e("Downloader","Size of Video : "+videos.size());*/
            if (videos != null && !videos.isEmpty()) 
                for (Video video : videos)
                
                    Log.e("Downloader", "ext : " + video.ext);
                    if (video.ext.toLowerCase().contains("mp4") || video.ext.toLowerCase().contains("3gp") || video.ext.toLowerCase().contains("flv") || video.ext.toLowerCase().contains("webm")) 
                        ext = video.ext.toLowerCase();
                        fVideos.add(new Video(video.ext,video.type,video.url));
                    
                


                return fVideos;
            
         catch (Exception e) 
            e.printStackTrace();
            Log.e("Downloader", "Couldn't get YouTube streaming URL", e);
        
        Log.e("Downloader", "Couldn't get stream URI for " + url);
        return null;
    

    @Override
    protected void onPostExecute(ArrayList<Video> streamingUrl) 
        super.onPostExecute(streamingUrl);
        progressDialog.dismiss();
        if (streamingUrl != null) 
            if (!streamingUrl.isEmpty()) 
                //Log.e("Steaming Url", "Value : " + streamingUrl);

                for (int i = 0; i < streamingUrl.size(); i++) 
                    Video fX = streamingUrl.get(i);
                    Log.e("Founded Video", "URL : " + fX.url);
                    Log.e("Founded Video", "TYPE : " + fX.type);
                    Log.e("Founded Video", "EXT : " + fX.ext);
                
                //new ProgressBack().execute(new String[]streamingUrl, filename + "." + ext);
            
        
    

public void initTypeMap()

    typeMap.put("13", new Meta("13", "3GP", "Low Quality - 176x144"));
    typeMap.put("17", new Meta("17", "3GP", "Medium Quality - 176x144"));
    typeMap.put("36", new Meta("36", "3GP", "High Quality - 320x240"));
    typeMap.put("5", new Meta("5", "FLV", "Low Quality - 400x226"));
    typeMap.put("6", new Meta("6", "FLV", "Medium Quality - 640x360"));
    typeMap.put("34", new Meta("34", "FLV", "Medium Quality - 640x360"));
    typeMap.put("35", new Meta("35", "FLV", "High Quality - 854x480"));
    typeMap.put("43", new Meta("43", "WEBM", "Low Quality - 640x360"));
    typeMap.put("44", new Meta("44", "WEBM", "Medium Quality - 854x480"));
    typeMap.put("45", new Meta("45", "WEBM", "High Quality - 1280x720"));
    typeMap.put("18", new Meta("18", "MP4", "Medium Quality - 480x360"));
    typeMap.put("22", new Meta("22", "MP4", "High Quality - 1280x720"));
    typeMap.put("37", new Meta("37", "MP4", "High Quality - 1920x1080"));
    typeMap.put("33", new Meta("38", "MP4", "High Quality - 4096x230"));

编辑 2:

此代码有时无法正常工作

同源政策

https://en.wikipedia.org/wiki/Same-origin_policy

https://en.wikipedia.org/wiki/Cross-origin_resource_sharing

problem of Same-origin policy. Essentially, you cannot download this file from www.youtube.com because they are different domains. A workaround of this problem is [CORS][1]. 

参考:https://superuser.com/questions/773719/how-do-all-of-these-save-video-from-youtube-services-work/773998#773998

url_encoded_fmt_stream_map // traditional: contains video and audio stream
adaptive_fmts              // DASH: contains video or audio stream

每一个都是逗号分隔的数组,我称之为“流对象”。每个“流对象”都会包含这样的值

url  // direct HTTP link to a video
itag // code specifying the quality
s    // signature, security measure to counter downloading

每个 URL 都将被编码,因此您需要对其进行解码。现在是棘手的部分。

YouTube 的视频至少有 3 个安全级别

unsecured // as expected, you can download these with just the unencoded URL
s         // see below
RTMPE     // uses "rtmpe://" protocol, no known method for these

RTMPE 视频通常用于官方全长电影,并受 SWF 验证类型 2 保护。自 2011 年以来一直存在,尚未进行逆向工程。

“s”类型的视频是最难下载的。您通常会在 VEVO 视频等上看到这些内容。它们以签名开头,例如

AA5D05FA7771AD4868BA4C977C3DEAAC620DE020E.0F421820F42978A1F8EAFCDAC4EF507DB5 然后用这样的函数对签名进行加扰

function mo(a) 
  a = a.split("");
  a = lo.rw(a, 1);
  a = lo.rw(a, 32);
  a = lo.IC(a, 1);
  a = lo.wS(a, 77);
  a = lo.IC(a, 3);
  a = lo.wS(a, 77);
  a = lo.IC(a, 3);
  a = lo.wS(a, 44);
  return a.join("")

这个函数是动态的,它通常每天都在变化。为了使其更加困难,该函数托管在诸如

之类的 URL 上

http://s.ytimg.com/yts/jsbin/html5player-en_US-vflycBCEX.js

这就引入了同源策略的问题。本质上,您无法从 www.youtube.com 下载此文件,因为它们是不同的域。此问题的解决方法是 CORS。使用 CORS,s.ytimg.com 可以添加此标头

Access-Control-Allow-Origin: http://www.youtube.com

它允许从 www.youtube.com 下载 javascript。他们当然不这样做。此解决方法的一种解决方法是使用 CORS 代理。这是一个代理,它以以下标头响应所有请求

Access-Control-Allow-Origin: *

所以,既然您已经代理了您的 JS 文件,并使用该函数来打乱签名,您可以在查询字符串中使用它来下载视频。

【讨论】:

以上是关于在 Java、Android 上从 Youtube 下载视频的代码的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 Android 上从 Xalan 调用 Java 扩展函数?

如何检测您是不是在android java的webview中点击了youtube播放按钮?

无法在 Android 中解析符号 Youtube

在 Android 上从照片 URI 创建文件

如何在 Android 上从蓝牙的 InputStream 中读取

如何在Android 7中实施YouTube Direct Lite