我们都知道它是 API 的一个缺失部分,那么你能做些什么来自己实现它呢?让我们想想 WebView 到底是什么:
嵌入式浏览器组件基于 WebKit [...]
默认情况下,WebKit 不支持网页渲染。为了渲染和显示 HTML 内容,Oracle 必须使用 Java Graphics 2D API 编写自己的渲染器。
这意味着引擎必须有实现。 WebKit 的实现位于com.sun.webkit 包(相当一部分类只是原生调用的包装器)。当然,在大多数情况下,您不想使用 com.sun.* 类,但目前您正在使用 JavaFX,所以这并不重要。
如果我们稍微了解一下 sun 的资源,我们可以找到 WCMediaPlayer.class 以及一些抽象的音频方法,例如:
protected abstract void setRate(float rate);
protected abstract void setVolume(float volume);
protected abstract void setMute(boolean mute);
protected abstract void setSize(int w, int h);
protected abstract void setPreservesPitch(boolean preserve);
来吧Java,让我叫你:
volumeMethod = WCMediaPlayer.class.getDeclaredMethod("setVolume", float.class);
volumeMethod.setAccessible(true);
感谢您的帮助,但我如何才能获得 WCMediaPlayer 实例?我们必须查看对new WCMediaPlayerImpl() 的引用。明白了! WCGraphicsManager.class通过fwkCreateMediaPlayer()方法创建MediaPlayer,毕竟是把指针和实例放在refMap中:
Field refMapField = WCGraphicsManager.class.getDeclaredField("refMap");
refMapField.setAccessible(true);
还好经理已经暴露了getGraphicsManager()方法来获取实例:
WCGraphicsManager graphicsManager = WCGraphicsManager.getGraphicsManager();
refMap = (Map<Integer, Ref>) refMapField.get(graphicsManager);
捕获的地图包含 Ref 实例(还有其他 WC* 实例),因此您必须过滤它们:
Collection<WCMediaPlayer> mediaPlayers = refMap.values().stream()
.filter(ref -> ref instanceof WCMediaPlayer)
.map(ref -> (WCMediaPlayer) ref)
.collect(Collectors.toList());
您可能期望工作示例,所以这是我使用的代码:
public class WebEngineTest extends Application {
private Map<Integer, Ref> refMap;
private Method volumeMethod;
@Override
@SuppressWarnings("unchecked")
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load("https://www.youtube.com/watch?v=hRAZBSoAsgs");
Field refMapField = WCGraphicsManager.class.getDeclaredField("refMap");
refMapField.setAccessible(true);
volumeMethod = WCMediaPlayer.class.getDeclaredMethod("setVolume", float.class);
volumeMethod.setAccessible(true);
WCGraphicsManager graphicsManager = WCGraphicsManager.getGraphicsManager();
refMap = (Map<Integer, Ref>) refMapField.get(graphicsManager);
Button button = new Button("Volume");
button.setOnAction(event -> setVolume(0.1f));
Group group = new Group();
group.getChildren().addAll(webView, button);
Scene scene = new Scene(group, 625, 625);
primaryStage.setScene(scene);
primaryStage.show();
}
public void setVolume(float volume) {
Collection<WCMediaPlayer> mediaPlayers = this.getMediaPlayers();
mediaPlayers.forEach(mediaPlayer -> setVolumeMethod(mediaPlayer, volume));
}
private void setVolumeMethod(Object instance, Object... args) {
try {
volumeMethod.invoke(instance, args);
} catch (Exception e) {
e.printStackTrace();
}
}
private Collection<WCMediaPlayer> getMediaPlayers() {
return refMap.values().stream()
.filter(ref -> ref instanceof WCMediaPlayer)
.map(ref -> (WCMediaPlayer) ref)
.collect(Collectors.toList());
}
}
最后,记住这些调用的顺序。例如,refMap 不包含所有Refs,直到引擎的状态不是SUCCEEDED 或WCGraphicsManager.getGraphicsManager() 如果根本没有创建图形元素则返回null。
或许,最好的方法是将这些解决方案结合起来。如果没有 JavaFX 提供的场景和糟糕的 API,很难支持 Web 技术组合。您也可以尝试嵌入其他浏览器,例如 Chromium。