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 D
onAction
处理程序不捕获
sound
字段,而是捕获
App
的封闭实例(即
App.this.sound
)。因此,我相信Java内存模型可以保证FX线程在上面的示例中看到最新的值,而不管JVM是否进行任何优化。如果我没有弄错的话,这就是为什么字段在lambdas/匿名类中使用时不必(有效地)是最终的,它们在多线程环境中定义了行为,而不像局部变量(加上heap vs stack)。尽管如此,我同意
任务将使事情变得更容易/更清楚。