我如何像其他网站一样绕过 YouTube API 嵌入限制?

How can I work around YouTube API embed restrictions like other websites?

我正在构建一个 java 程序,该程序可以选择在嵌入式播放器中播放 YouTube 视频。 问题是大多数音乐视频无法播放,我收到以下错误: "This video contains content from (Media Corporation Name). It is restricted from playback on certain sites."

我尝试在 Chrome 中加载相同的 URL 并得到相同的结果。 https://www.youtube.com/embed/TMZi25Pq3T8

然而,经过一些研究,我很快通过安装 Chrome 扩展修复了它,该扩展允许我添加 HTTP 请求 Headers 并添加了 Referer header 遵循此结构“https://www..com”并使其正常工作。

所以我想一定是这样。 我添加了以下代码,以便将请求 headers 添加到我的 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());

还是没有成功,同样的错误信息。

我用来通过 API、Discogs 提取有关发行版数据的网站也可以播放 "restricted" 视频。我在这里错过了什么?

后期编辑: 进一步说明:

我想为我犯的错误道歉:

  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 与请求 header 混淆了。

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

来设置用户代理

我在这里找到了一个 hack,但这对我不起作用:https://twitter.com/codingfabian/status/524942996748652544

谢谢!

我设法通过使用 javassist and this tutorial 如何 工具 Java 代码解决了这个问题。

正如我在问题中所述,YouTube 播放器需要 Referer header 来播放一些视频(例如 VEVO、Sony Music Enternatinment 等拥有的音乐视频) .).

我所做的是从 URLLoader class 中拦截 prepareConnection 方法 JavaFX 的 WebEngine 并在方法 body:

的顶部插入我的指令
c.setRequestProperty("Referer", "https://www.discogs.com");

(再次说明,请按照 tutorial 的所有说明进行操作)

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

这是我的两个 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 = ".setRequestProperty(\"Referer\", \"https://www.discogs.com\");"; // Confused about there being "" instead of "c"? Please read below
                method.insertBefore(src);
                byteCode = ctClass.toBytecode();
                ctClass.detach();
            }  catch (Exception e) {
                e.printStackTrace();
            }
        }   
        return byteCode;
    }
}

这就是为什么我使用“$1”来访问方法参数,而不是 "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 [=17=], , , ... described below. Accessing local variables declared in the method is not allowed although declaring a new local variable in the block is allowed.

完整的 javassist 教程可以在 here.

中找到

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

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

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

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

我希望这对外面的人有所帮助。我知道我在这个问题上花了几天时间。 我不知道这是否是最好的方法,但它为我完成了工作。 不过,这让我想知道为什么没有一种直接的方法来为 JavaFX 的 WebEngine 设置 Request Headers...

稍后编辑:

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

我用的是ea-agent-loader (JAR download link).

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

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);
    }
}

MainClass 中我的 main 方法 如下所示:

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

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