【问题标题】:Mute a WebView in JavaFX, is it possible?在JavaFX中静音一个WebView,有可能吗?
【发布时间】:2018-08-22 00:12:53
【问题描述】:

非常简单。是否可以静音或控制 JavaFX WebView 的音量?我用谷歌搜索了一段时间,但我找不到任何提及这一点。我查看了WebViewWebEngine 的代码,似乎没有关于控制音量的任何内容。

我仍然需要同一个应用程序中的其他MediaPlayers 来工作和产生声音,所以我不能将整个应用程序静音。

【问题讨论】:

  • 我们是否假设声音仅来自 WebView 内容中的 <video><audio> 元素?
  • @VGR:我没有,但总比没有好。
  • 根据您显示的网页的性质,以及在某种程度上您加载它们的方式,通过WebEngine.executeScript(...) 执行一些 Javascript 应该非常简单。
  • 所以一旦网页被加载,你可以做,例如webEngine.executeScript("document.getElementsByTagName('video').forEach(function(vid) { vid.muted=true;});");(我的 Javascript 技能有点生疏,可能不是 100% 正确,但你应该明白了。如果网页可能在页面加载后插入音频/视频,那么它可能会变得有点棘手。
  • 在 JDK 问题跟踪器中有 an unresolved issue 关于这个问题已经存在大约 6 年了!

标签: java javafx webview javafx-webview


【解决方案1】:

目前,WebViewWebEngine 控件中没有这样的声音系统API。

但您可以注入 JavaScript 来静音/取消静音和控制声音。此方法仅适用于 HTML5 元素 ,因为像 FlashJS 这样的项目初始化的音频通常是不可能限制的。我已经为你创建了示例程序:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        WebView myWebView = new WebView();
        WebEngine engine = myWebView.getEngine();
        //dirty code xD

        Button btn = new Button("Load Youtube");
        btn.setOnAction((ActionEvent event) -> engine.load("http://www.youtube.com/embed/A6hrw6KGdtM?autoplay=1"));

        Button btn2 = new Button("Mute");
        btn2.setOnAction((ActionEvent event) -> engine.executeScript(getJSAudioVideo(true)));

        Button btn3 = new Button("Unmute");
        btn3.setOnAction((ActionEvent event) -> engine.executeScript(getJSAudioVideo(false)));

        Button btn4 = new Button("+");
        btn4.setOnAction((ActionEvent event) -> engine.executeScript(getJSSoundVolume(0.9)));

        Button btn5 = new Button("-");
        btn5.setOnAction((ActionEvent event) -> engine.executeScript(getJSSoundVolume(0.1)));

        VBox root = new VBox();
        root.getChildren().addAll(myWebView, btn, btn2, btn3, btn4, btn5);

        Scene scene = new Scene(root, 800, 500);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Mute a WebView in JavaFX, is it possible?");

        primaryStage.show();
    }

    /**
     * @param mute
     * @return JS code for mute/unmute
     */
    public String getJSAudioVideo(boolean mute){
        return "var videos = document.querySelectorAll('video'),\n" +
                "    audios = document.querySelectorAll('listen');\n" +
                "    [].forEach.call(videos, function(video) { video.muted = "+String.valueOf(mute)+"; });\n" +
                "    [].forEach.call(audios, function(audio) { audio.muted = "+String.valueOf(mute)+"; });";
    }

    /**
     * @param volume
     * @return JS code for setting volume sound
     */
    public String getJSSoundVolume(double volume){
        //max 1.0, min 0.0 Default: 1.0
        double _volume = (volume > 1.0 || volume < 0.0) ? 1.0 : volume;

        return "var videos = document.querySelectorAll('video'),\n" +
                "    audios = document.querySelectorAll('listen');\n" +
                "    [].forEach.call(videos, function(video) { video.volume = "+String.valueOf(_volume)+"; });\n" +
                "    [].forEach.call(audios, function(audio) { audio.volume = "+String.valueOf(_volume)+"; });";

    }

    public static void main(String[] args) {
        launch(args);
    }
}

【讨论】:

    【解决方案2】:

    我们都知道它是 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,直到引擎的状态不是SUCCEEDEDWCGraphicsManager.getGraphicsManager() 如果根本没有创建图形元素则返回null

    或许,最好的方法是将这些解决方案结合起来。如果没有 JavaFX 提供的场景和糟糕的 API,很难支持 Web 技术组合。您也可以尝试嵌入其他浏览器,例如 Chromium。

    【讨论】:

    • 狡猾而聪明的^^。但是您的代码对我不起作用,我使用的是 Java 9 和大部分 com.sun.* APIs are unsupported 。 com.sun.webkit 不可用。
    • 他们试图删除和阻止更多,例如反射或不安全:/ 无论如何,他没有指定他的环境,即使他想在 Java 9 上运行它,他也可以使用--add-exports 标志
    • 我使用的是 Java 8,我理解这种黑客行为会将您与单个版本联系起来。我不明白的是您对引擎不在SUCCEEDED 状态的评论。
    • 我的意思是管理器在加载状态时添加 Refs,所以你应该等到它结束加载。 'SUCCEEDED' 或 'RUNNING' 等状态描述页面加载的当前状态,它来自 LoadWorker 监听器
    【解决方案3】:

    WebView,它是Node类的扩展。它封装 WebEngine 对象,将 HTML 内容合并到应用程序的场景中,并提供应用效果和转换的属性和方法。在 WebView 对象上调用的 getEngine() 方法返回与其关联的 Web 引擎。 Source

    所以 WebView 目前不支持任何用于静音/音量控制的 API。有一个 API 可以将 MediaPlayer 中加载的内容静音(即)

    获取 muteProperty 状态:Source link

    public BooleanProperty muteProperty()
    

    检查静音条件:Source link

    public final boolean isMute()
    

    要将视频静音,请将值设置为 true:Source link

    public final void setMute(boolean value)
    

    或者使用 JavaScript API 将视频/音频静音:

    getElementById("Your video Id");
    

    并将控件设置为静音:

    vid.muted = true;
    

    Source Link

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-26
      • 2013-11-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多