JSF Concurrency - Launch threads from managed beans to call Service - multithreading

I have a ManagedBean that can call a Service that accesses the database. That service has the #Service and #Transactional annotations, and since it is a service that has functions to mess around in the database, it obviously is called in a number of places.
In my frontend, in my managed bean I want to call for a function that is like a job, a batch, and it can take quite a while. The job can take like 1 minute and I don't want the client to be waiting on the frontend that long, since there may be jobs that can take like an hour.
I'll post an example of the code I have currently (just consider I have in my xhtml file some button that calls the launchBatch() function):
#ManagedBean
#RequestScoped
public class JobLauncherController {
#ManagedProperty("#{serviceController}")
private ServiceController serviceController;
public void launchBatch(String code) {
switch (code) {
case Constants.Batch.BATCH_1:
Thread t1 = new Thread(new Runnable() {
public void run() {
serviceController.getBatchService().batch1();
}
});
t1.start();
break;
case Constants.Batch.BATCH_2:
Thread t2 = new Thread(new Runnable() {
public void run() {
Date date = new Date();
String parameters = String
.valueOf(date.getHours() + ":" + date.getMinutes() +
":" + date.getSeconds());
serviceController.getBatchService().batch2(parameters);
}
});
t2.start();
break;
case Constants.Batch.BATCH_3:
Thread t3 = new Thread(new Runnable() {
public void run() {
serviceController.getBatchService().batch3();
}
});
t3.start();
break;
case Constants.Batch.BATCH_4:
Thread t4 = new Thread(new Runnable() {
public void run() {
serviceController.getBatchService().batch4();
}
});
t4.start();
break;
default:
break;
}
showGrowl();
}
public void showGrowl() {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "",
"Job launched sucessfully!");
FacesContext.getCurrentInstance().addMessage("growlPanel", message);
}
public ServiceController getServiceController() {
return serviceController;
}
public void setServiceController(ServiceController serviceController) {
this.serviceController = serviceController;
}
}
My service (interface):
public interface AppBatchService {
public void batch1();
public void batch2(Object parameters);
public void batch3();
public void batch4();
}
And the implementation
#Service
#Transactional
public class AppBatchServiceImpl implements AppBatchService {
//code to implementation goes here
}
So this is launching a thread successfully, because when I debug this, I have put the functions batchN to sleep for like a minute, and the code keeps flowing to my showGrowl() and my showGrowl(), ends the code flow successfully, the problem is at frontend, because it doesn't complete the action of the button while the thread I launched is running.
Is there a specific way to start threads when dealing with JSF/Primefaces to end the action, without changing the service I have? I've seen solutions with #Stateless and with #Asynchronous on the functions, but I cannot change this structure.

Related

Flutter Platform Channels - Invoke channel method on android, hangs the ui

I'm trying to use Tesseract in flutter using the following package https://github.com/arrrrny/tesseract_ocr
I've download the app and run in.
The problem is that the extractText hangs the UI.
Looking at the Java code:
Thread t = new Thread(new Runnable() {
public void run() {
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getUTF8Text();
baseApi.end();
}
});
t.start();
try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); }
result.success(recognizedText[0]);
I can see that it is running on a new thread, so I expect it not to hang the app, but it still does.
I found this example:
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Call the desired channel message here.
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
result.success(recognizedText[0]);
}
});
from https://flutter.dev/docs/development/platform-integration/platform-channels#channels-and-platform-threading
but it also hangs the UI.
The docs also say
**Channels and Platform Threading**
Invoke all channel methods on the platform’s main thread when writing code on the platform side.
Can someone clarify this sentence?
According to Richard Heap answer, I tried to call a method from native to dart, passing the result:
Dart side:
_channel.setMethodCallHandler((call) {
print(call);
switch (call.method) {
case "extractTextResult":
final String result = call.arguments;
print(result);
}
var t;
return t;
});
Java side:
channel.invokeMethod("extractTextResult","hello");
if I call this method from the main thread, this works fine, but then the thread is blocking.
If I do
Thread t = new Thread(new Runnable() {
public void run() {
channel.invokeMethod("extractTextResult","test1231231");
}
});
t.start();
result.success("tst"); // return immediately
Then the app crashes with the following message:
I also tried:
Thread t = new Thread(new Runnable() {
public void run() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Call the desired channel message here.
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
result.success(recognizedText[0]);
// channel.invokeMethod("extractTextResult", "test1231231");
}
});
}
});
t.start();
result.success("tst");
which is what I understand that Richard Heap last comment meant, but It still hangs the ui.
I had the same Issue and fixed it with a MethodCallWrapper in TesseractOcrPlugin.java
This Code works for me (no Dart-code change is needed):
package io.paratoner.tesseract_ocr;
import com.googlecode.tesseract.android.TessBaseAPI;
import android.os.Handler;
import android.os.Looper;
import android.os.AsyncTask;
import java.io.File;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/** TesseractOcrPlugin */
public class TesseractOcrPlugin implements MethodCallHandler {
private static final int DEFAULT_PAGE_SEG_MODE = TessBaseAPI.PageSegMode.PSM_SINGLE_BLOCK;
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "tesseract_ocr");
channel.setMethodCallHandler(new TesseractOcrPlugin());
}
// MethodChannel.Result wrapper that responds on the platform thread.
private static class MethodResultWrapper implements Result {
private Result methodResult;
private Handler handler;
MethodResultWrapper(Result result) {
methodResult = result;
handler = new Handler(Looper.getMainLooper());
}
#Override
public void success(final Object result) {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.success(result);
}
});
}
#Override
public void error(final String errorCode, final String errorMessage, final Object errorDetails) {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.error(errorCode, errorMessage, errorDetails);
}
});
}
#Override
public void notImplemented() {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.notImplemented();
}
});
}
}
#Override
public void onMethodCall(MethodCall call, Result rawResult) {
Result result = new MethodResultWrapper(rawResult);
if (call.method.equals("extractText")) {
final String tessDataPath = call.argument("tessData");
final String imagePath = call.argument("imagePath");
String DEFAULT_LANGUAGE = "eng";
if (call.argument("language") != null) {
DEFAULT_LANGUAGE = call.argument("language");
}
calculateResult(tessDataPath, imagePath, DEFAULT_LANGUAGE, result);
} else {
result.notImplemented();
}
}
private void calculateResult(final String tessDataPath, final String imagePath, final String language,
final Result result) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
final String[] recognizedText = new String[1];
final TessBaseAPI baseApi = new TessBaseAPI();
baseApi.init(tessDataPath, language);
final File tempFile = new File(imagePath);
baseApi.setPageSegMode(DEFAULT_PAGE_SEG_MODE);
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getUTF8Text();
baseApi.end();
result.success(recognizedText[0]);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}
}.execute();
}
}
By using join you're making the main thread wait for the background thread, blocking it. You have to remove the join and return a result immediately.
So, how do you return the ocr result, which won't be available immediately. When it becomes available, you then call a method from native to dart, passing the result. At the dart end, you then handle the result as any async event.
The point of the last paragraph of your question is that your result will become available on your background thread, so you'd want to call the native to dart method there. You can't. You have to post the method call code to the main looper - you already show some code for posting to the main looper which you can use as an example.
Based on Richard Heap answer I came up with this:
Dart code:
_channel.setMethodCallHandler((call) {
switch (call.method) {
case "extractTextResult":
final String result = call.arguments;
print(result);
}
var t;
return t;
});
Java code:
Thread t = new Thread(new Runnable() {
public void run() {
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
channel.invokeMethod("extractTextResult", recognizedText[0]);
}
});
}
});
t.start();
result.success("tst");
explain:
This code will run the Java extractText in a separate thread, and when the result is ready it will hopp back to the ui thread with the call to Looper.getMainLooper() which will then send the message back to the Dart side which must receive the message on the ui thread, which is what this message means:
**Channels and Platform Threading**
Invoke all channel methods on the platform’s main thread when writing code on the platform side.
NOTE on the Dart side, this is still incomplete example since you then need to report to the ui that a message received, this can be done with a Completer, which is used to create and complete a future
At the end of your method channel just return the response back to dart side
Add this line at the end of method channel result.success(true)
full example
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"method-channel"
).setMethodCallHandler { call, result ->
if (call.method == "getFirebaseAppCheckDebugToken") {
...
result.success(true) // just add this line
}
}
}```

Can you access a Hazelcast Queue from within an ItemListener?

I have a use case where I have a set of items, DiagnosticRuns, that are submitted to my cluster. I want to process them serially (to avoid conflicts). I am trying to use a Hazelcast Queue protected by a Lock to make sure the items are processed one at a time. Hazelcast is running in embedded mode in my cluster. If I register an ItemListener with the Queue, is it safe to call take() on the Queue from within the itemAdded() method? For example:
#Component
public class DistributedQueueListener
{
public static final String DIAGNOSTICS_RUN_QUEUE_NAME = "diagnosticRun";
#Autowired
private HazelcastInstance hazelcast;
#Autowired
private ProductVersioningService productVersioningService;
private IQueue<DiagnosticRun> diagnosticRunQueue;
private ILock diagnosticRunLock;
private String diagnosticRunListenerId;
#PostConstruct
public void init()
{
diagnosticRunQueue = hazelcast.getQueue(DIAGNOSTICS_RUN_QUEUE_NAME);
diagnosticRunLock = hazelcast.getLock("diagnosticRunLock");
diagnosticRunListenerId = diagnosticRunQueue.addItemListener(new DiagnosticRunListener(), false);
}
#PreDestroy
public void stop()
{
diagnosticRunQueue.removeItemListener(diagnosticRunListenerId);
}
public class DiagnosticRunListener implements ItemListener<DiagnosticRun>
{
#Override
public void itemAdded(ItemEvent<diagnosticRun> item)
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll();
if(diagnosticRun != null)
{
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
}
finally
{
diagnosticRunLock.unlock();
}
}
#Override
public void itemRemoved(ItemEvent<diagnosticRun> item)
{
}
}
}
I'm not sure whether it's threadsafe to call take() on the Queue from that location and thread.
If that is not allowed, I'll have to set up my own long-running loop to poll() the Queue. I'm not sure what's the best way to set up a long-running thread in a Spring Boot application. Assuming the method above does not work, would the below code be threadsafe? Or is there a better way to do this?
#Component
public class DistributedQueueListener
{
public static final String DIAGNOSTIC_RUN_QUEUE_NAME = "diagnosticRun";
#Autowired
private HazelcastInstance hazelcast;
#Autowired
private ProductVersioningService productVersioningService;
private IQueue<diagnosticRun> diagnosticRunQueue;
private ILock diagnosticRunLock;
private ExecutorService executorService;
#PostConstruct
public void init()
{
diagnosticRunQueue = hazelcast.getQueue(DIAGNOSTIC_RUN_QUEUE_NAME);
diagnosticRunLock = hazelcast.getLock("diagnosticRunLock");
executorService = Executors.newFixedThreadPool(1);
executorService.submit(() -> listenToDiagnosticRuns());
}
#PreDestroy
public void stop()
{
executorService.shutdown();
}
private void listenToDiagnosticRuns()
{
while(!executorService.isShutdown())
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll(1L, TimeUnit.SECONDS);
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
catch(InterruptedException e)
{
logger.error("Interrupted polling diagnosticRun queue", e);
}
finally
{
diagnosticRunLock.unlock();
}
}
}
}
First I'll qualify that I'm not exactly an expert on which threads these are executed on and when so some may disagree but here're my thoughts on this so anyone please chime in as this looks to be an interesting case. Your first solution mixes the Hazelcast event threading with it's operation threading. In fact you're triggering three operations to be invoked as a result of the single event. If you put some arbitrary latency in your call to updateProcductDeviceTable, you'll see that eventually, it will slow down but resume up again after some time. This will cause your local event queue to pile up while operations are invoked. You could put everything you're doing in a separate thread which you can "wake" up on #itemAdded or if you can afford to have a bit of latency, do what you're doing on your second solution. I would, however, make a couple changes in
listenToDiagnosticsRuns() method:
private void listenToDiagnosticRuns()
{
while(!executorService.isShutdown())
{
if(diagnosticRunQueue.peek() != null)
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll(1L, TimeUnit.SECONDS);
if(diagnosticRun != null)
{
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
}
catch(InterruptedException e)
{
logger.error("Interrupted polling diagnosticRun queue", e);
}
finally
{
diagnosticRunLock.unlock();
}
} // peek != null
else
{
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
//do nothing
}
}
}
}

Javafx Updating UI from a Thread Java 8

I interested in one interesting task. I have UI in JavaFx with another thread which updates UI. I started updates from Platform.runLater. Code:
private void startUpdateDaemon() {
updateUserStatus();
updateTable();
}
private void startUpdateDaemonTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(() -> {
startUpdateDaemon();
});
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
startUpdateDaemonTask();
}
Also I have place in another class where I updates UI:
private void startUpdateDaemonTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run() {
updateGameStatus();
}
});
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
So, finally I have two places with call "Platform.runLater" and different methods inside.
My question is Can I create only "one" method with one time call "Platform.runLater" and send to this method different methods which will be call ?? May be I can write finish method with consumers and send to him methods 'startUpdateDaemon()' and 'updateGameStatus()'?
Thanks a lot.
You can add a Runnable parameter to your method. This parameter is given to you Platform.runLater:
private void startUpdateDaemonTask(Runnable runner) {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(runner);
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Now you can invoke this method with your method references:
startUpdateDaemonTask(this::startUpdateDaemon);
startUpdateDaemonTask(this::updateGameStatus);

Implementing JComponent blinking in Java using threads

I am trying to implement a program where I want different Components to blink at different speeds. I am using threads. But its not working.
How can I implement this.
This is the void run function in the class that implements runnable
public void run()
{
try
{
while(true)
{
Thread.sleep(1000);
if(isVisible()==true)
{
setVisible(false);
}
else
{
setVisible(true);
}
repaint();
}
}
catch(InterruptedException e)
{
}
}
}
and this is the class (its in a paint component of the main JPanel)where I call the threads-
{
cars[i]=new Car(color, xLocation, yLocation, speed, type, i, widthController, heightController);
cars[i].setBounds(widthController+(xLocation*50)+10, heightController+(yLocation*50)+10, 30, 30);
add(cars[i]);
threads[i]=new Thread(cars[i]);
threads[i].start();
}
cars is an array of JComponents of which void run is part of.
Thanks
With Swing, all operations that affect visible components should be run on the AWT-EventQueue. This is a dedicated thread for Input/Output operations as well as drawing and component operations. My recommendation is to use a swing timer for your run operation. The repaint call you made will call the paintCompnent method on the AWT-EventQueue. However you're changing the state of visibility on a seperate thread. This means that by the time the repaint call is made, it's possible the state has already changed to the previous value.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
//Rest of code above...
//This will execute the timer every 500 milliseconds
Timer aTimer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent pE) {
aComponent.setVisible(!aComponent.isVisible());
}
});
aTimer.start();
Another option is that on each thread add this call:
//This should be added inside of your thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
aComponent.setVisible(!aComponent.isVisible());
}
});
Here's the answer I was alluding to in my comments:
public void run()
{
try
{
while(true)
{
Thread.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
setVisible(!isVisible());
}
}
});
}
catch(InterruptedException e)
{
}

How to play fragments of mp3 with JavaFX?

I want to develop a Java program playing an mp3-file in a specific manner. I marked a number of fragments in this file with startTime and endTime. The program should play the first fragment and then sleep for 5 seconds. Then play the second fragment and sleep again. And so on. I use JavaFX class MediaPlayer. The program prototype is as follows:
import javafx.application.Application;
import javafx.stage.Stage;
import java.io.FileNotFoundException;
import java.io.IOException;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;
import java.util.concurrent.TimeUnit;
public class JavaFXMediaPlayer02 extends Application {
#Override
public void start(Stage stage) throws FileNotFoundException,IOException,InterruptedException {
Media media = new Media("file:///D:/1016_00.mp3");
MediaPlayer mediaPlayer = new MediaPlayer(media);
//Set and play the first fragment of mp3-file
mediaPlayer.setStartTime(Duration.millis(1219.0));
mediaPlayer.setStopTime(Duration.millis(2728.0));
mediaPlayer.play();
System.out.println("1st fragment played!");
TimeUnit.SECONDS.sleep(5);
//Set and play the second fragment
mediaPlayer.setStartTime(Duration.millis(3947.0));
mediaPlayer.setStopTime(Duration.millis(6629.0));
mediaPlayer.play();
System.out.println("2nd fragment played!");
TimeUnit.SECONDS.sleep(5);
//Set and play the second fragment
mediaPlayer.setStartTime(Duration.millis(7453.0));
mediaPlayer.setStopTime(Duration.millis(10704.0));
mediaPlayer.play();
System.out.println("3rd fragment played!");
}
public static void main(String[] args) {
launch(args);
}
}
But I only hear the 3rd fragment. What's the matter? Why don't I hear the first and the second fragments? How to correct my program? Isn't JavaFX an appropriate tool for my task?
The problem here lies in the TimeUnit.SECONDS.sleep(5); invokation. This method sets the current thread into sleep. And in your case this thread is the JavaFX Application Thread. That causes the whole application to "freeze" (which would be more obviously if you added some GUI-Elements) and therefore the mediaPlayer.play(); commands are executed, but are instantly "freezed" because of the sleep function. After the `TimeUnit.SECONDS.sleep(5); calls, you set new start and end times for your MediaPlayer and execute play() again, so that the track starts at the new start time. Thats why only your last fragment is played.
Now to the solution:
You should never invoke Thread.sleep() or similar methods on the JavaFX App Thread. But in your case you have to wait a certain amount of time between playing the fragments. The first approach would be invoke Thread.sleep() or TimeUnit.SECONDS.sleep(5); on a new thread and call the Mediaplayer methods on the JFX App Thread. But that doesn't work properly because you haven't set up an "order" in which the threads are called. There are various ways to do this (via Semaphores, Locks and Conditions, JavaFX Concurrency and so on...)
I tried to solve your problem by doing some quick-and-dirty programming, but i came across a problem with mediaPlayer.setStopTime(Duration.millis());. It does not seem to work on my computers, so that the files are always played to the end. I added a stop button to simulate the automatic stopping.
The following class sets the new start and endpoints and plays the fragment. If the mediaplayer is stops, it calls the next fragment on the LittleMediaScheduler class.
public class LittleMediaHelper implements Runnable {
public double startTime;
public double endTime;
public MediaPlayer player;
public int id;
public LittleMediaScheduler scheduler;
public LittleMediaHelper(double startTime, double endTime,
MediaPlayer player, int id) {
this.startTime = startTime;
this.endTime = endTime;
this.player = player;
this.id = id;
}
public LittleMediaScheduler getScheduler() {
return scheduler;
}
public void setScheduler(LittleMediaScheduler scheduler) {
this.scheduler = scheduler;
}
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
player.setStartTime(Duration.millis(startTime));
player.setStopTime(Duration.millis(endTime));
System.out.println(player.getStartTime());
System.out.println(player.getStopTime());
player.play();
player.setOnStopped(new Runnable() {
#Override
public void run() {
int idtmp = id + 1;
System.out.println("NEXT " + idtmp);
scheduler.call(idtmp);
}
});
}
});
}
}
This class is responsibly for sleeping a certain amount on a new thread and after successfully sleeping invoking the next LittleMediaHelper class play functionality.
public class LittleMediaScheduler {
private ArrayList<LittleMediaHelper> hArrL;
private int SLEEPTIME = 2000;
public LittleMediaScheduler(LittleMediaHelper... helpers) {
this.hArrL = new ArrayList<>();
for (LittleMediaHelper h : helpers) {
h.setScheduler(this);
System.out.println(h.startTime);
this.hArrL.add(h);
}
System.out.println(hArrL.size());
}
public void init() {
Thread t = new Thread(this.hArrL.get(0));
t.start();
}
public void call(final int id) {
Thread t = new Thread(new Task<String>() {
#Override
protected String call() throws Exception {
Thread.sleep(SLEEPTIME);
return null;
}
#Override
protected void succeeded() {
super.succeeded();
System.out.println("Next playing...");
if (id > LittleMediaScheduler.this.hArrL.size() - 1) {
return;
}
LittleMediaHelper next = LittleMediaScheduler.this.hArrL
.get(id);
Thread nextT = new Thread(next);
nextT.start();
}
});
t.start();
}
}
The main class with a stop button. Without mediaPlayer.pause() the player somehow repeats one step twice although new start end endpoints are set. Don't know if this is a bug or not.
public class JavaFXMediaPlayer02 extends Application {
#Override
public void start(Stage stage) throws FileNotFoundException, IOException,
InterruptedException {
Media media = new Media("file:///C:/test.mp3");
final MediaPlayer mediaPlayer = new MediaPlayer(media);
LittleMediaHelper phase1 = new LittleMediaHelper(0, 1000, mediaPlayer,
0);
LittleMediaHelper phase2 = new LittleMediaHelper(50000, 55000,
mediaPlayer, 1);
LittleMediaHelper phase3 = new LittleMediaHelper(200000, 200500,
mediaPlayer, 2);
LittleMediaScheduler scheduler = new LittleMediaScheduler(phase1,
phase2, phase3);
scheduler.init();
Group g = new Group();
Button b = new Button("STOP");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
mediaPlayer.pause();
mediaPlayer.stop();
}
});
g.getChildren().add(b);
Scene sc = new Scene(g);
stage.setScene(sc);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Resources