Java音频剪辑:带有远程URL的新音频剪辑会造成UI延迟
将Java音频剪辑:带有远程URL的新音频剪辑会造成UI延迟,java,javafx,Java,Javafx,将new AudioClip与s一起使用,外部用户界面字符串将从远程服务器下载该剪辑。这可能需要几秒钟 如果在JavaFX应用程序线程上执行此操作,则应用程序可能会变得无响应 防止长时间下载在JavaFX应用程序线程上的首选解决方案是什么 例如: 导入javafx.application.application; 导入javafx.scene.scene; 导入javafx.scene.layout.StackPane; 导入javafx.scene.media.AudioClip; 导入jav
new AudioClip
与s
一起使用,外部用户界面字符串将从远程服务器下载该剪辑。这可能需要几秒钟
如果在JavaFX应用程序线程上执行此操作,则应用程序可能会变得无响应
防止长时间下载在JavaFX应用程序线程上的首选解决方案是什么
例如:
导入javafx.application.application;
导入javafx.scene.scene;
导入javafx.scene.layout.StackPane;
导入javafx.scene.media.AudioClip;
导入javafx.stage.stage;
公共类主扩展应用程序{
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公共无效开始(阶段primaryStage){
长时间=System.currentTimeMillis();
音频剪辑声音=新的音频剪辑(“https://dl.dropboxusercontent.com/s/qq8vvfqx2l8sljb/Dice%201.wav?dl=0");
System.out.println(“所用时间(毫秒):”+(System.currentTimeMillis()-Time));
setScene(新场景(newstackpane(),300250));
primaryStage.show();
}
}
以这种方式创建
AudioClip
会将我的计算机上的JavaFX线程冻结约2-3秒。任何预计需要超过几毫秒的操作都需要移出JavaFX应用程序线程,因为否则,它将冻结您的UI,直到完成为止,如果我要从远程URL加载音频文件,我会在一个新线程中这样做,如下所示:
import java.util.concurrent.ExecutionException;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.scene.media.AudioClip;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
StackPane root = new StackPane(new ProgressIndicator());
Button play = new Button("play");
Task<AudioClip> loadAudio = new Task<AudioClip>() {
@Override
protected AudioClip call() throws Exception {
return new AudioClip("https://dl.dropboxusercontent.com/s/qq8vvfqx2l8sljb/Dice%201.wav?dl=0");
}
};
loadAudio.setOnSucceeded(successEvent -> {
play.setOnAction(actionEvent -> {
try {
loadAudio.get().play();
} catch (InterruptedException | ExecutionException x) {
//Handle Exceptions
x.printStackTrace();
}
});
root.getChildren().setAll(play);
});
new Thread(loadAudio).start();
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
import java.util.concurrent.ExecutionException;
导入javafx.application.application;
导入javafx.concurrent.Task;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.ProgressIndicator;
导入javafx.scene.layout.StackPane;
导入javafx.scene.media.AudioClip;
导入javafx.stage.stage;
公共类应用程序扩展应用程序{
@凌驾
公共无效开始(阶段primaryStage){
StackPane root=newstackpane(newprogressindicator());
按钮播放=新按钮(“播放”);
Task loadAudio=新任务(){
@凌驾
受保护的AudioClip调用()引发异常{
返回新的音频剪辑(“https://dl.dropboxusercontent.com/s/qq8vvfqx2l8sljb/Dice%201.wav?dl=0");
}
};
loadAudio.SetonSucceed(成功事件->{
播放设置动作(动作事件->{
试一试{
loadAudio.get().play();
}捕获(InterruptedException | ExecutionException x){
//处理异常
x、 printStackTrace();
}
});
root.getChildren().setAll(播放);
});
新线程(loadAudio.start();
原始阶段。设置场景(新场景(根,300250));
primaryStage.show();
}
}
请注意,在某些情况下,您的应用程序需要向用户指示它正在后台执行某些操作,并且用户需要等待操作完成。在本例中,当从远程URL下载音频时,我使用了一个简单的进度指示器
注意:我使用Task编辑了解决方案(以前的版本和我测试过的这个版本一样工作正常)在后台线程中加载..所以音频剪辑不需要在JavaFX应用程序线程中初始化?老实说,我不知道-但它不是场景图的一部分,加载至少应该是好的,如果你想这样做的话
sound
应该是volatile
。对于任务
@James\u D来说,这似乎是一个很好的用例,我不确定volatile
在这里是绝对必要的;我希望调用runLater
来创建必要的先发生后发生关系(尽管文档没有做出这样的保证,但我无法看到如何在不创建后台线程和FX线程之间的先发生后发生关系的情况下实现它)。@Slaw我认为在实践中几乎肯定没有必要,因为JVM需要进行的优化非常复杂。设置sound
的值和将play
按钮添加到用户界面之间存在一种先发生后发生的关系,但我认为这在技术上还不够。(将play.setOnAction(…)
移动到play.setOnAction.runLater(…)
平台可能也足够了…?)。当然,使用任务
意味着我们不必考虑所有这些。。。这就是为什么最好使用更高级的API。@James\u DonAction
处理程序不捕获sound
字段,而是捕获App
的封闭实例(即App.this.sound
)。因此,我相信Java内存模型可以保证FX线程在上面的示例中看到最新的值,而不管JVM是否进行任何优化。如果我没有弄错的话,这就是为什么字段在lambdas/匿名类中使用时不必(有效地)是最终的,它们在多线程环境中定义了行为,而不像局部变量(加上heap vs stack)。尽管如此,我同意任务将使事情变得更容易/更清楚。