如何像其他网站一样解决 YouTube API 嵌入限制? [英] How can I work around YouTube API embed restrictions like other websites?

查看:29
本文介绍了如何像其他网站一样解决 YouTube API 嵌入限制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个 java 程序,它可以选择在嵌入式播放器中播放 YouTube 视频.问题是大多数音乐视频都无法播放,并且出现以下错误:该视频包含来自(媒体公司名称)的内容.它在某些网站上被限制播放."

我尝试在 Chrome 中加载相同的 URL 并得到相同的结果.

所以我认为一定是这样.我添加了以下代码,以便将请求标头添加到我的 JavaFX WebView/WebEngine:

URI uri = URI.create("https://www.youtube.com/embed/TMZi25Pq3T8");列表<字符串>cookies = new ArrayList<>();cookies.add("用户代理=BDM/v0.92");cookies.add("Referer=https://www.youtube.com");映射<字符串,列表<字符串>>headers = new LinkedHashMap<String, List<String>>();headers.put("Set-Cookie", cookies);尝试 {CookieHandler.getDefault().put(uri, headers);} 捕捉(IOException ex){ex.printStackTrace();}System.out.println(webView.getEngine().getUserAgent());webView.getEngine().load(uri.toString());

仍然,没有成功,同样的错误信息.

我用来通过 APIDiscogs 提取发布数据的网站也能够播放受限"视频.我在这里错过了什么?

稍后进一步说明:

我想为我所犯的错误道歉:

  1. System.out.println(webView.getEngine().getUserAgent()); 行不像我第一次说的那样打印BDM/v0.92",它打印的是默认的 JavaFX用户代理,Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/538.19 (KHTML, like Gecko) JavaFX/8.0 Safari/538.19".这导致了 2 号
  2. 正如 Roman Nazarenko 所指出的,我将 cookie 与请求标头混淆了.

这引出了一个真正的问题,我如何为 JavaFX WebEngine 发送 HTTP 请求标头?唯一的选择是通过调用 webView.getEngine().setUserAgent("myUserAgent"); 来设置用户代理;

我在这里发现了一个 hack,但这对我不起作用:

(同样,请按照教程所有说明)

(注意:尽管上面的教程很好地解释了概念,它并没有真正涉及到 MANIFEST.MF 文件的作用和结构,所以请检查 此链接了解有关这方面的更多信息)

这是我的两个课程:

MyJavaAgent.java

包 com.busytrack.discographymanager.headerfixagent;导入 java.lang.instrument.Instrumentation;公共类 MyJavaAgent {公共静态无效premain(字符串agentArgument,仪器仪表){ClassTransformer 转换器 = new ClassTransformer();instrumentation.addTransformer(变压器);}}

ClassTransformer.java

包 com.busytrack.discographymanager.headerfixagent;导入 java.io.ByteArrayInputStream;导入 java.lang.instrument.ClassFileTransformer;导入 java.lang.instrument.IllegalClassFormatException;导入 java.security.ProtectionDomain;导入 javassist.ClassPool;导入 javassist.CtClass;导入 javassist.CtMethod;公共类 ClassTransformer 实现 ClassFileTransformer {public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {byte[] byteCode = classfileBuffer;if (className.equals("com/sun/webkit/network/URLLoader")) {尝试 {ClassPool classPool = new ClassPool(true);CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));CtMethod 方法 = ctClass.getDeclaredMethod("prepareConnection");String src = "$1.setRequestProperty("Referer", "https://www.discogs.com");";//对$1"而不是c"感到困惑?请阅读下文方法.insertBefore(src);byteCode = ctClass.toBytecode();ctClass.detach();} 捕捉(异常 e){e.printStackTrace();}}返回字节码;}}

这就是为什么我使用$1"而不是c"来访问方法参数的原因:

<块引用>

语句和块可以引用字段和方法.如果该方法是使用 -g 选项编译的(以在类文件中包含局部变量属性),则它们还可以引用它们插入的方法的参数.否则,他们必须通过下面描述的特殊变量 $0, $1, $2, ... 访问方法参数.尽管允许在块中声明新的局部变量,但不允许访问在方法中声明的局部变量.

整个javassist教程可以在这里找到.

将这两个类和 MANIFEST.MF 文件打包到单独的 JAR 中后,将其导入您的 IDE(我使用 Eclipse)并添加以下 VM 参数:

-javaagent:./(your-jar-name).jar

在 Eclipse 中,您可以像这样添加 VM 参数:

右击你的项目->运行方式->运行配置... ->打开参数选项卡->插入你的虚拟机参数->申请

我希望这对那里的人有所帮助.我知道我在这个问题上花了几天时间.我不知道这是否是最好的方法,但它对我有用.不过,这让我想知道为什么没有一种直接的方法来为 JavaFX 的 WebEngine 设置请求标头...

后期

我发现了一种更简洁更简单的方法来动态加载 Java 代理,而无需创建单独的 JAR 清单文件,导入它们,在启动时传递 -javaagent VM 参数等.

我使用了 ea-agent-loader (JAR下载链接).

在您的 IDE 中导入 JAR 并将 MyJavaAgent 类(具有 premain 方法的类)更改为:

包 com.busytrack.discographymanager.headerfixagent;导入 java.lang.instrument.Instrumentation;公共类 MyJavaAgent {公共静态无效agentmain(字符串agentArgument,仪器仪表){ClassTransformer 转换器 = new ClassTransformer();instrumentation.addTransformer(变压器);}}

我的 MainClass 中的 ma​​in 方法如下所示:

public static void main(String[] args) {AgentLoader.loadAgentClass(MyJavaAgent.class.getName(), null);//加载 MyJavaAgent 类启动(参数);//启动 JavaFX 应用程序}

我希望能够动态加载代理,因为使用静态方法需要我为所有平台创建单独的启动器并在启动时传递 -javaagent 参数.现在,我可以像往常一样从 eclipse 中导出一个可运行的 JAR,并且代理会自动加载(不需要 VM 参数).感谢 BioWare 提供这个工具!:D

I am building a java program that has the option to play YouTube videos in an embedded player. The problem is that most of the music videos won't play and I get the following error: "This video contains content from (Media Corporation Name). It is restricted from playback on certain sites."

I tried loading the same URL in Chrome and got the same results. https://www.youtube.com/embed/TMZi25Pq3T8

However, after some research, I quickly got it fixed by installing a Chrome Extension that allows me to add HTTP Request Headers and added a Referer header that follows this structure "https://www..com" and got it working.

So I thought that must be it. I added the following code in order to add request headers to my JavaFX WebView / WebEngine:

URI uri = URI.create("https://www.youtube.com/embed/TMZi25Pq3T8");
List<String> cookies = new ArrayList<>();
cookies.add("User-Agent=BDM/v0.92");
cookies.add("Referer=https://www.youtube.com");
Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
headers.put("Set-Cookie", cookies);
try {
    CookieHandler.getDefault().put(uri, headers);
} catch (IOException ex) {
    ex.printStackTrace();
}
System.out.println(webView.getEngine().getUserAgent());
webView.getEngine().load(uri.toString());

Still, no success, the same error message.

The website that I'm using to extract data about releases through their API, Discogs, is able to play "restricted" videos as well. What am I missing here?

LATER EDIT: Further clarifications:

I would like to apologize for the mistakes I made:

  1. The line System.out.println(webView.getEngine().getUserAgent()); doesn't print "BDM/v0.92" as I first stated, it prints the default JavaFX user agent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/538.19 (KHTML, like Gecko) JavaFX/8.0 Safari/538.19". And this leads to number 2
  2. As Roman Nazarenko pointed out, I was confusing cookies with request headers.

This leads to the real question, how can I send HTTP Request headers for JavaFX WebEngine? The only option is to set the user agent by calling webView.getEngine().setUserAgent("myUserAgent");

I found a hack here but this didin't work for me: https://twitter.com/codingfabian/status/524942996748652544

Thanks!

解决方案

I managed to solve the issue by using javassist and this tutorial on how to instrument Java code.

As I stated in my question, the YouTube player needs a Referer header to play some videos (like music videos owned by VEVO, Sony Music Enternatinment, etc.).

What I did is I intercepted prepareConnection method from the URLLoader class that is used by JavaFX's WebEngine and inserted my instruction at the top of the method body:

c.setRequestProperty("Referer", "https://www.discogs.com");

(Again, please follow the tutorial for all the instructions)

(Note: Even though the tutorial above is explains very well the concepts, it doesn't really touch much on the role and structure of a MANIFEST.MF file, so please check this link for more info about this aspect)

These are my two classes:

MyJavaAgent.java

package com.busytrack.discographymanager.headerfixagent;
import java.lang.instrument.Instrumentation;
public class MyJavaAgent {
public static void premain(String agentArgument, Instrumentation instrumentation) {
    ClassTransformer transformer = new ClassTransformer();
    instrumentation.addTransformer(transformer);
    }
}

ClassTransformer.java

package com.busytrack.discographymanager.headerfixagent;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class ClassTransformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;
        if (className.equals("com/sun/webkit/network/URLLoader")) {
            try {
                ClassPool classPool = new ClassPool(true);
                CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                CtMethod method = ctClass.getDeclaredMethod("prepareConnection");
                String src = "$1.setRequestProperty("Referer", "https://www.discogs.com");"; // Confused about there being "$1" instead of "c"? Please read below
                method.insertBefore(src);
                byteCode = ctClass.toBytecode();
                ctClass.detach();
            }  catch (Exception e) {
                e.printStackTrace();
            }
        }   
        return byteCode;
    }
}

This is why I used "$1" to access the method parameter, instead of "c":

The statement and the block can refer to fields and methods. They can also refer to the parameters to the method that they are inserted into if that method was compiled with the -g option (to include a local variable attribute in the class file). Otherwise, they must access the method parameters through the special variables $0, $1, $2, ... described below. Accessing local variables declared in the method is not allowed although declaring a new local variable in the block is allowed.

The entire javassist tutorial can be found here.

After packing the two classes and the MANIFEST.MF file in a separate JAR, import it in your IDE (I used Eclipse) and add the following VM argument:

-javaagent:./(your-jar-name).jar

In Eclipse, you can add VM arguments like this:

right click on your project -> Run As -> Run Configurations... -> open the Arguments tab -> insert your VM argument -> Apply

I hope this helps someone out there. I know I spent a few days on this issue. I don't know if this is the best approach but it does the job for me. Still, it makes me wonder why isn't there a straightforward way of setting Request Headers for JavaFX's WebEngine...

Later edit:

I found a much cleaner and easier approach for loading Java Agents, dynamically, without the need to create a separate JAR, manifest file, importing them, passing the -javaagent VM parameter at startup, etc.

I used the ea-agent-loader (JAR download link).

Import the JAR in your IDE and change the MyJavaAgent class (the one that had the premain method) to this:

package com.busytrack.discographymanager.headerfixagent;
import java.lang.instrument.Instrumentation;
public class MyJavaAgent {
    public static void agentmain(String agentArgument, Instrumentation instrumentation) {
        ClassTransformer transformer = new ClassTransformer();
        instrumentation.addTransformer(transformer);
    }
}

My main method from the MainClass looks like this:

public static void main(String[] args) {
    AgentLoader.loadAgentClass(MyJavaAgent.class.getName(), null); // Load the MyJavaAgent class
    launch(args); // Start the JavaFX application
}

I wanted to be able to load the Agent dynamically because, using the static method required me to create separate launchers for all platforms and pass the -javaagent parameter on startup. Now, I can export a runnable JAR from eclipse like I usually do and the agent will load automatically (no VM parameters required). Thanks, BioWare for this tool! :D

这篇关于如何像其他网站一样解决 YouTube API 嵌入限制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆