Very simple. Is it possible to mute or control the volume of a JavaFX WebView? I googled for a while but I can't find any mention of this. I looked at the code for WebView
and WebEngine
and there doesn't seem to be anything about controlling the volume.
I still need other MediaPlayer
s in the same app to work and produce sound, so, I can't mute the whole application.
Right now, there is NO such API for sound system in WebView or WebEngine Control.
But you could inject JavaScript to mute/unmute and control sounds. This method, only works with HTML5 elements < video > or < audio >, because items like Flash or JS initialized audio is impossible to restrict in general. I have created example program for you:
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);
}
}
We all know that it is a missing part of API, so what can you do to implement it on your own? Let's think about what WebView really is:
The embedded browser component is based on WebKit [...] By default WebKit doesn't support web page rendering. In order to render and display HTML content Oracle had to write its own renderer using Java Graphics 2D API.
It means that the engine has to have implementation. Implementation of WebKit is located in com.sun.webkit
package (a considerable part of classes is just a wrapper for native calls). Of course, in most cases you don't want to use com.sun.*
classes, but currently you're working with JavaFX, so it doesn't really matter.
If we jump a little in sun's sources, we can find WCMediaPlayer.class
with some abstract audio methods like:
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);
C'mon Java, let me call you:
volumeMethod = WCMediaPlayer.class.getDeclaredMethod("setVolume", float.class);
volumeMethod.setAccessible(true);
I appreciate your help but how can I get WCMediaPlayer
instances? We have to look at references to new WCMediaPlayerImpl()
. Gotcha! WCGraphicsManager.class
creates MediaPlayer by fwkCreateMediaPlayer()
method, after all, it puts pointer and instance in refMap
:
Field refMapField = WCGraphicsManager.class.getDeclaredField("refMap");
refMapField.setAccessible(true);
Fortunately, the manager has exposed getGraphicsManager()
method to get an instance:
WCGraphicsManager graphicsManager = WCGraphicsManager.getGraphicsManager();
refMap = (Map<Integer, Ref>) refMapField.get(graphicsManager);
Catched map contains Ref
instances (there are also other WC*
instances) so you have to filter them:
Collection<WCMediaPlayer> mediaPlayers = refMap.values().stream()
.filter(ref -> ref instanceof WCMediaPlayer)
.map(ref -> (WCMediaPlayer) ref)
.collect(Collectors.toList());
You probably expect working example so here is code that I used:
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());
}
}
Finally, remember about the order of these calls. For instance, refMap
doesn't contain all Refs
until the state of engine is not SUCCEEDED
or WCGraphicsManager.getGraphicsManager()
returns null
if graphic element is not created at all.
Probably, the best way is to combine these solutions. It's hard to support web technology mix without scenerio and poor API provided by JavaFX. You can also try to embed another browser, like Chromium.
To say about WebView , it is an extension of the Node class. It encapsulates a WebEngine object, incorporates HTML content into an application's scene, and provides properties and methods to apply effects and transformations. The getEngine() method called on a WebView object returns a web engine associated with it. Source
So WebView doesn't support any APIs for mute/volume control right now. There is an API to mute the content loaded in the MediaPlayer (ie.,)
To get the status of muteProperty: Source link
public BooleanProperty muteProperty()
To check the mute condition : Source link
public final boolean isMute()
To mute the video set the value to true: Source link
public final void setMute(boolean value)
OR otherwise use the JavaScript API to mute the Video/Audio:
getElementById("Your video Id");
and set the control to Mute by :
vid.muted = true;
Source Link
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With