I need to use a text to speech library. I decided to use https://responsivevoice.org/ . The integration is fairly easy, however, since my project uses GWT it is - obviously - not so straight forward.
Here is my java code that is a minimal proof of concept class:
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.*;
public class InverseVoiceTrainer extends SimplePanel implements ClickHandler {
Button playBtn;
public InverseVoiceTrainer() {
ScriptInjector.fromUrl("https://code.responsivevoice.org/responsivevoice.js").setCallback(
new Callback<Void, Exception>() {
#Override
public void onSuccess(Void result) {
GWT.log("ResponsiveVoiceJS loaded.");
}
#Override
public void onFailure(Exception reason) {
GWT.log("ResponsiveVoiceJS loading FAILED!");
}
}).inject();
playBtn = new Button("Play");
playBtn.addClickHandler(this);
this.add(playBtn);
}
#Override
public void onClick(ClickEvent event) {
GWT.log("Onclick pressed");
playWord("This is a test message...");
}
public static native void playWord(String s) /*-{
console.log("playWord - 1");
responsiveVoice.speak(s);
console.log("playWord - 2");
}-*/;
}
So, looking into the console log I can see the following:
ResponsiveVoice r1.5.3
SuperDevModeLogger.java:71 ResponsiveVoiceJS loaded.
SuperDevModeLogger.java:71 Onclick pressed
InverseVoiceTrainer.java:40 playWord - 1
InverseVoiceTrainer.java:42 playWord - 2
Which tells me that (a) ResponsiveVoice seems to have been correctly loaded and (b) the sound should have been played. However, I do not hear anything and my sound volume is hearable. So, what is going wrong here?
Well, this might be not the best answer, but it worked for me.
First, just link the responsivevoice.js in hosting html page:
<script type="text/javascript" language="javascript" src="https://code.responsivevoice.org/responsivevoice.js"></script>
Then remove the ScriptInjector part of your code.
The last change is to add $wnd in playWord() method:
$wnd.responsiveVoice.speak(s);
Tested. Worked.
Related
I have a problem. I have two classes in same javafx package. A single html file with javascript at the head section, a java class(extending Application). Now the problem is when i tried to click the button after the page is displayed in the javafx webview, nothing is updated in the webView. Below is the code for the two file. Please i need to know why it isn't working. i have been debugging this problem since 8hrs now, no success. thanks in advance.
java class
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class JavaFXApplication25 extends Application {
// inner class
public class Adder
{
public double add(double n, double m)
{
return n + m;
}
}
#Override
public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException {
WebView w = new WebView();
WebEngine e = w.getEngine();
e.setJavaScriptEnabled(true);
e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm());
Scene scene = new Scene(new VBox(w));
primaryStage.setScene(scene);
primaryStage.show();
// make javascript aware of java object
e.getLoadWorker().stateProperty().addListener(
(p, o, n) ->{
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
}
);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The html file
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript">
function addNum(){
var n1 = Number(document.getElementById('num1').value);
var n2 = Number(document.getElementById('num2').value);
var n3 = adder.add(n1, n2);
document.getElementById('r').innerHTML = n3;
}
</script>
</head>
<body>
<input type="text" id="num1" />
<input type="text" id="num2" />
<p> <span id="r"> </span></p>
<button onclick="addNum()" >Add</button>
</body>
The point is that the programs runs and displays the page, but on pressing the button, nothing is updated on the page
I even tried to make the upcall before loading the html page, yet, no success. Please someone should help check the bug in the code. Thanks once again.
Now below is the output after been run. It shows nothing even after when the Add button is clicked several times! No error message on the standard console, nothing nothing!
output
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
Welcome to the execrable pain in the ass that is DOM event sync between JavaFX and DOM stuff.
The worker state "SUCCEEDED" does not necessarily mean that the DOM is loaded in the page.
The DOM is loaded at document.onLoad() ... and even so it's kind of a can of worms since not all content is loaded by that time (images and stuff).
JQuery has a convenient JQuery(document).ready(function (e){ /*do whatever*/ }) routine you can use to trigger something that lets JavaFX know that it's ready to do stuff in the DOM (basically what you tried to accomplish with the code above [..]Worker.State.SUCCEEDED[..])
Essentially the sequence of events is something like:
1. JavaFX => Worker.state reaches SUCCEEDED STATE
2. DOM ====> Starts converting HTML into DOM components
3. ??? ====> This step is a complete mystery unbeknownst to even the savvy-est of skript kiddies. but i'm pretty sure this is where zuckerberg gets access to your webcam for a few frames. ... so beware!
4. DOM ====> Finishes doing its thing and fires off the onLoad() event which triggers the document.load() listener and associated function.
5. DOM ====> Officially inovkes the first JS function it comes across (not always the one you'd think it'd come across because JS is a magic programming language that doesn't give a shit about threads, or sequencing thereof, and anything goes when it comes JOs)
6. DOM+JS => Continues loading for some reason (i am not kidding)
7. DOM+JS => Some other weird flibbetlygibbletly bullshit done by the DOM and whatever javascript runs inside it ... if you're using timers... good luck.
8. DOM+JS => at this point, jQuery (if used) intercepts the .ready() event listener.
9. DOM+JS => believe me... stuff still isn't completely loaded. (because rampant JS scripts can add DOM components as they see fit and no .ready or .load event listener will ever be called.)
To add insult to injury, this sequence of events can take anywhere between 1ms and 1 minute to reach (8)... it's arbitrary as hell.
I wound up writing code which starts comparing node trees for changes in order to figure out when exactly to start messing with the DOM.... but i digress.
What you want is to make sure you initiate your JS-JavaFX communication protocol once the DOM is ready to run your custom javascript.
Put that shit in document.onLoad(), listen for it inside JavaFX, and then set the adder as you did so already... then hope for the best.
Thanks everyone...After a little research, i was able to come up with an extremely simple solution..Here it goes in steps:
declare a private field (which is an object of the class that contains the public methods to be called from javascript) in the main class.
instantiate the private field
add the member to the web engine after it loads
call the java method from the javascript.
so here goes the updated code
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class JavaFXApplication25 extends Application {
// inner class
public class Adder
{
public double add(double n, double m)
{
return n + m;
}
}
// STEP 1: Now declare the private field (COMPULSORY)
private Adder adder;
#Override
public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException {
WebView w = new WebView();
WebEngine e = w.getEngine();
e.setJavaScriptEnabled(true);
e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm());
Scene scene = new Scene(new VBox(w));
primaryStage.setScene(scene);
primaryStage.show();
//STEP 2: instantiate the private js object from step1
adder = new Adder();
// make javascript aware of java object
e.getLoadWorker().stateProperty().addListener(
(p, o, n) ->{
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
}
);
}
/**
#param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The HTML code still remains the same as before.
//Now, i think there is a bug in this aspect of javafx with jdk 8 and above. I am specifically using jdk 8_126...version. It seems there is automatic destruction of the javascript handler class by the jvm. Fortunately, wrapping the js hanler as a private field in the main class seems to prevent this terrible occurrence.
I am currently working on a JavaFX based application, where users can interact with places that are marked on a world map. To do this, I am using an approach similiar to the one described in http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html ([1]).
However, I am facing a hard-to-debug problem related to the Javascript callback variable injected to the embedded HTML-page using the WebEngine's setMember() method (see also https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/js-javafx.htm ([2]) for an official tutorial).
When running the program for a while, the callback variable is loosing its state unpredictably! To demonstrate this behaviour, I developed a minimal working/failing example. I am using jdk1.8.0_121 64-bit on a Windows 10 machine.
The JavaFx App looks as follows:
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Application;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewJsCallbackTest extends Application {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
public static void main(String[] args) {
launch(args);
}
public class JavaScriptBridge {
public void callback(String data) {
System.out.println("callback retrieved: " + data);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
primaryStage.setScene(new Scene(new AnchorPane(webView)));
primaryStage.show();
final WebEngine webEngine = webView.getEngine();
webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());
webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("javaApp", new JavaScriptBridge());
}
});
webEngine.setOnAlert(event -> {
System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
});
}
}
The HTML file "page.html" looks as follows:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<!-- use for in-browser debugging -->
<!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> -->
<script type="text/javascript">
var javaApp = null;
function javaCallback(data) {
try {
alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data);
javaApp.callback(data);
} catch (e) {
alert("caugt exception: " + e);
}
}
</script>
</head>
<body>
<button onclick="javaCallback('Test')">Send data to Java</button>
<button onclick="setInterval(function(){ javaCallback('Test'); }, 1000)">Send data to Java in endless loop</button>
</body>
</html>
The state of the callback variable javaApp can be observed by clicking on the "Send data to Java in endless loop" button. It will continuously try to run the callback method via javaApp.callback, which produces some logging message in the Java app. Alerts are used as an additional communication channel to back things up (always seems to work and currently used as work-around, but that's not how things are ment to be...).
If everything is working as supposed, each time logging similiar to the following lines should be printed:
callback retrieved: Test
2017/01/27 21:26:11 alerted: javaApp=webviewtests.WebViewJsCallbackTest$JavaScriptBridge#51fac693(type=object), data=Test
However, after a while (anything from 2-7 minutes), no more callbacks are retrieved, but only loggings like the following line are printed:
2017/01/27 21:32:01 alerted: javaApp=undefined(type=object), data=Test
Printing the variable now gives 'undefined' instead of the Java instance path. A strange observation is that the state of javaApp is not truly "undefined". using typeof returnsobject, javaApp === undefined evaluates to false. This is in accordance with the fact that the callback-call does not throw an exception (otherwise, an alert starting with "caugt exception: " would be printed).
Using Java VisualVM showed that the time of failure happens to coincide with the time the Garbage Collector is activated. This can be seen by observing the Heap memory consumption, which drops from approx. 60MB to 16MB due to GC.
What's goining on there? Do you have any idea how I can further debug the issue? I could not find any related know bug...
Thanks a lot for your advice!
PS: the problem was reproduced much faster when including Javascript code to display a world map via Leaflet (cf [1]). Loading or shifting the map most of the time instantly caused the GC to do its job. While debugging this original issue, I traced the problem to the minimal example presented here.
I solved the problem by creating an instance variable bridge in Java that holds the JavaScriptBridge instance sent to Javascript via setMember(). This way, Gargbage Collection of the instance is prevented.
Relevant code snippet:
public class JavaScriptBridge {
public void callback(String data) {
System.out.println("callback retrieved: " + data);
}
}
private JavaScriptBridge bridge;
#Override
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
primaryStage.setScene(new Scene(new AnchorPane(webView)));
primaryStage.show();
final WebEngine webEngine = webView.getEngine();
webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());
bridge = new JavaScriptBridge();
webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("javaApp", bridge);
}
});
webEngine.setOnAlert(event -> {
System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
});
}
Altough the code now works smoothly (also in conjunction with Leaflet), I am still irritated of this unexpected behaviour...
Edit: The explanation for this behaviour is documented since Java 9 (thanks #dsh for your clarifying comment! I was working with Java 8 at the time and unfortunately didn't have this information at hand...)
I have a JavaFX application that makes heavy use of a WebView. I am trying to insert an object into the DOM that the JavaScript code can use, and I need these objects to be available as new pages are loaded.
However, when I run the program, FirebugLite shows the object in the DOM, but the functions do not execute.
According to some Oracle documentation, this seems to be the appropriate way to provide upcalls from JavaScript to Java. I've also seen a few StackOverflow posts explaining the same thing.
What am I missing? I'm using Java 8, Update 51, 64-bit on Windows 7.
Java:
public class DemoApplication extends Application {
Debug debug;
#Override
public void start(final Stage stage) throws Exception {
debug = new Debug();
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
JSObject windowObject = (JSObject) webEngine.executeScript("window");
windowObject.setMember("Debug", debug);
}
}
}
);
webEngine.load("http://localhost:8080/page1.html");
stage.setScene(new Scene(browser));
stage.show();
}
}
public class Debug {
public void print(final Object text) {
System.err.println(text);
}
}
HTML/JavaScript:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="https://getfirebug.com/firebug-lite.js"></script>
<script>
Debug.print("Hello");
</script>
</head>
<body>
Page 1
Page 2
</body>
</html>
Firebug Screenshot:
I believe what is happening is that WebEngine loads the page, the ChangeListener is invoked at various points (SCHEDULED, RUNNING, SUCCEEDED, etc.). Once the Worker.State.SUCCEEDED event happens, the page has already finished loading all content and has finished executing that content as well. So basically my calls to Debug.print() in the JavaScript code were happening early and calling on an object that was undefined or null.
That's my best guess anyway, because if I add a JavaScript function that is executed by the Java portion after adding in the objects, everything works as expected.
This is how I modified the JavaScript side:
<script>
// callback that uses java objects
window.ready = function() {
Debug.print("Hello");
}
</script>
And this is how I modified the Java side:
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
JSObject windowObject = (JSObject) webEngine.executeScript("window");
windowObject.setMember("Debug", debug); // insert object
windowObject.call("ready"); // execute callback
}
}
}
);
The key changes here are the ready() function in the JavaScript, and invoking that function after injecting the objects on the Java side. This ensures those objects are available before being called.
I tried this on a few different pages and when going from page to page, when the ready() function was called Debug.print() executed properly, even when using WebEngine.reload(), or WebHistory.go().
I am building a web application using GWT and SMartGWT and I need to integrate an external script for a Photo Gallery: http://slideshow.triptracker.net/.
My current attempt is:
ScriptInjector.fromUrl("http://slideshow.triptracker.net/slide.js").setCallback(
new Callback<Void, Exception>() {
public void onFailure(Exception reason) {
Window.alert("Script load failed.");
}
public void onSuccess(Void result) {
Window.alert("Script load success.");
}
}).inject();
Afterwards, I call this method:
Code:
native static void example(String p) /*-{
$wnd.viewer = new $wnd.PhotoViewer();
$wnd.viewer.add(p)
$wnd.viewer.show(0);
}-*/;
This does not give any error, but the photogallery appears BEHIND my Entry Point Layout, so it's not possible to use it.
So, I am trying to find a workaround and I would like to refer to the main layout from Javasript, to run the script on that. Is this possible? I've tried with
foo.getElement().getId();
but it returns me something like
isc_VLayout_0_wrapper
However, Javascript doesn't like it.
Then I tried
foo.getDOM.getId();
isc_1
But the result doesn't change.
Can someone help me fix this issue?
Thank You.
I think here are two things, that are possibly wrong:
You have an onSuccess() callback you can call the jsni call from within.
//1
ScriptInjector.fromUrl("http://slideshow.triptracker.net/slide.js").setCallback(
new Callback<Void, Exception>() {
public void onFailure(Exception reason) {
//3a
}
public void onSuccess(Void result) {
example("works");
//3b
}
}).inject();
//2
example("not loaded");
You may also try to call .setWindow(ScriptInjector.TOP_WINDOW) before calling .inject()
I have a problem: when I use webview with javascript interface to comunication between my activity and the html page, my actionbarsherlock stop working. And the problem is in this line:
myWebView.addJavascriptInterface(new JsObject(), "injectedObject");
the problems include List navigations and menu items with ActionView .
Basically if I comment the above code, everything works normally.
Some pictures to explain:
My Activity with sherlockactionbar and webview
My actionbar not working when when I'm calling the method addJavascriptInterface in my webview
My actionbar working when I remove the method addJavascriptInterface
How can I solve this problem???? Thanks.
Oh, I figured out my problem looking at this question
Basically I had a method accessed by javascript that disguised a view. So this occasioned UI errors.
Before:
class JsObject {
#JavascriptInterface
public void cancelProgress() {
progressbar.setVisibility(View.GONE);
}
}
After:
class JsObject {
#JavascriptInterface
public void cancelProgress() {
runOnUiThread(new Runnable(){
#Override
public void run(){
progressbar.setVisibility(View.GONE);
}
});
}
}