【问题标题】:How can I work around YouTube API embed restrictions like other websites?如何像其他网站一样解决 YouTube API 嵌入限制?
【发布时间】:2016-08-23 09:11:40
【问题描述】:

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

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

然而,经过一番研究,我很快通过安装一个允许我添加 HTTP 请求标头的 Chrome 扩展程序解决了这个问题,并添加了一个遵循此结构“https://www..com”的 Referer 标头并得到它工作。

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

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

我用来通过 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 , 像壁虎) JavaFX/8.0 Safari/538.19"。这导致了 2 号
  2. 正如 Roman Nazarenko 所指出的,我将 cookie 与请求标头混淆了。

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

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

谢谢!

【问题讨论】:

  • 错误是否来自 API 调用?如果是这样,您可能需要检查在提交请求时应该发送哪些参数。这样您就可以在 API 抛出错误的情况下识别任何缺失的参数。
  • 您在谈论标头,但您在此处设置 cookie。这些不是一回事。
  • 你试过读这个吗? stackoverflow.com/questions/13407482/…。此外,我还找到了这个 YouTube 工程和开发人员博客:youtube-eng.blogspot.co.uk/2011/12/…。它可能会对你有所帮助。

标签: java http youtube-api javafx-webengine


【解决方案1】:

我设法通过使用javassistthis tutorial 解决了这个问题,以了解如何instrument Java 代码。

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

我所做的是从 JavaFX 的 WebEngine 使用的 URLLoader 类中截获 prepareConnection 方法,并将我的指令插入到方法体的顶部:

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

(同样,请按照 tutorial 获取所有说明)

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

这是我的两个课程:

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

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

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

整个javassist教程可以在here找到。

将两个类和 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 设置请求标头...

后期编辑:

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

我使用了ea-agent-loader (JAR download link)。

在您的 IDE 中导入 JAR 并将 MyJavaAgent 类(具有 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 中的 ma​​in 方法如下所示:

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

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

【讨论】:

    猜你喜欢
    • 2011-10-10
    • 1970-01-01
    • 2015-09-08
    • 2014-07-13
    • 1970-01-01
    • 2011-10-12
    • 2012-12-15
    • 2019-12-18
    • 2018-04-18
    相关资源
    最近更新 更多