我正在使用 JavaFX(IntelliJ) 创建一个媒体播放器。现在我使用滑块作为进度条,以便我可以在任何所需的位置进行搜索。现在,每当我运行应用程序并启动播放器时,进度条与媒体一起正常工作。但是,当我尝试在媒体中的任何特定位置进行搜索时,它不起作用。我无法弄清楚这里出了什么问题。
package com.example.jmediaplayer;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.util.Duration;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.File;
import java.net.URL;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
public class MediaPlayer implements Initializable {
@FXML
private AnchorPane background;
@FXML
private Label mediaTitle, appTitle;
@FXML
private Button mediaActionButton, mediaNextbutton, mediaPreviousButton, mediaResetButton, openFileButton;
@FXML
private ProgressBar progressBar;
@FXML
private Slider progressSlider;
@FXML
private ComboBox<String> speedBox;
@FXML
private Slider volumeBar;
@FXML
private VBox progressParent, titleParent;
@FXML
private HBox controlBar, mediaSelect;
@FXML
private MediaView mediaView;
@FXML
private ImageView imageView;
private Media media;
private javafx.scene.media.MediaPlayer mediaPlayer;
private boolean isPlaying = false, isSeeking = false;
private File file;
private final String[] speeds = {"0.25x", "0.5x", "1x", "1.25x", "1.5x", "1.75x", "2x"};
private Timer timer;
private TimerTask task;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
setLayout();
// mediaActionButton.setGraphic(new FontIcon());
progressSlider.setMin(0);
progressSlider.setMax(1);
progressSlider.setValueChanging(true);
progressSlider.valueChangingProperty().addListener((observable, wasChanging, changing) -> {
// If the slider is currently being changed by the user
// set to true else false
System.out.println(changing);
isSeeking = changing;
});
progressSlider.valueProperty().addListener((observableValue, number, newValue) -> {
if (isSeeking) {
System.out.println("Now I am seeking");
double newTime = newValue.doubleValue() * media.getDuration().toMillis();
mediaPlayer.seek(Duration.millis(newTime));
}
});
for (String speed : speeds) {
speedBox.getItems().add(speed);
}
speedBox.setOnAction(this::changeSpeed);
mediaView.setFocusTraversable(true);
volumeBar.valueProperty().addListener((observableValue, number, t1) -> mediaPlayer.setVolume(volumeBar.getValue() * 0.01));
}
void setLayout() {
progressBar.prefWidthProperty().bind(progressParent.prefWidthProperty());
progressBar.prefWidthProperty().bind(progressParent.widthProperty());
progressSlider.prefWidthProperty().bind(progressParent.widthProperty());
controlBar.prefWidthProperty().bind(progressParent.widthProperty());
appTitle.prefWidthProperty().bind(titleParent.widthProperty());
mediaSelect.prefWidthProperty().bind(titleParent.widthProperty());
mediaView.fitHeightProperty().bind(background.minHeightProperty());
mediaView.fitWidthProperty().bind(background.minWidthProperty());
}
@FXML
void changeSpeed(ActionEvent event) {
if (speedBox.getValue() == null) {
mediaPlayer.setRate(1);
} else {
mediaPlayer.setRate(Double.parseDouble(speedBox.getValue().substring(0, speedBox.getValue().length() - 1)));
}
}
@FXML
void mediaAction(ActionEvent event) {
if (isPlaying) {
cancelTimer();
mediaPlayer.pause();
mediaActionButton.setText("Play");
} else {
beginTimer();
changeSpeed(null);
mediaPlayer.setVolume(volumeBar.getValue() * 0.01);
mediaPlayer.play();
mediaActionButton.setText("Pause");
}
isPlaying = !isPlaying;
}
@FXML
void mediaNext(ActionEvent event) {
}
@FXML
void mediaPrevious(ActionEvent event) {
}
@FXML
void mediaReset(ActionEvent event) {
}
void beginTimer() {
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
double current = mediaPlayer.getCurrentTime().toMillis();
double end = media.getDuration().toMillis();
progressBar.setProgress(current / end);
progressSlider.setValue(current / end);
if (current / end == 1) {
cancelTimer();
}
}
};
timer.scheduleAtFixedRate(task, 0, 100);
}
void cancelTimer() {
timer.cancel();
}
@FXML
void selectFile(ActionEvent event) {
FileChooser fileChooser = new FileChooser();
ExtensionFilter extensionFilter1 = new ExtensionFilter("MP4 files (*.mp4)", "*.mp4");
ExtensionFilter extensionFilter2 = new ExtensionFilter("MP3 files (*.mp3)", "*.mp3");
fileChooser.getExtensionFilters().add(extensionFilter1);
fileChooser.getExtensionFilters().add(extensionFilter2);
fileChooser.setSelectedExtensionFilter(extensionFilter2);
file = fileChooser.showOpenDialog(null);
if (file != null) {
String extension = getFileExtension(file);
mediaTitle.setText(file.getName());
media = new Media(file.toURI().toString());
mediaPlayer = new javafx.scene.media.MediaPlayer(media);
progressBar.setMinWidth(200);
if ("mp3".equalsIgnoreCase(extension)) {
mediaView.setVisible(false);
imageView.setVisible(true);
String imagePath = "/com/example/jmediaplayer/images/bg-mp3.jpg";
Image bg_for_mp3 = new Image(Objects.requireNonNull(getClass().getResource(imagePath)).toExternalForm());
imageView.setImage(bg_for_mp3);
} else if ("mp4".equalsIgnoreCase(extension)) {
imageView.setVisible(false);
mediaView.setVisible(true);
mediaView.setMediaPlayer(mediaPlayer);
}
if (isPlaying) {
mediaAction(null);
}
progressBar.setProgress(0);
progressSlider.setValue(0);
}
}
String getFileExtension(File file) {
int index = file.getName().lastIndexOf('.');
if (index > 0) {
return file.getName().substring(index+1);
}
return "";
}
}
我尝试使用Slider的valueChangingProperty()来设置changeListener。但这不起作用。我一定错过了一些东西,但不知道是什么。
我注意到您的代码中有一些可以改进的地方。
根据您当前的代码,我假设以下内容:
如果拖动
Slider
的拇指,则可以进行查找。
但是,如果单击
Slider
的轨道,则查找 不起作用。
valueChanging
属性才会变为 true。在其他任何地方,该属性仍然为 false,尽管该值已被用户交互更改。我不确定这是否是一个错误。无论如何,上述行为意味着我们需要跟踪用户是否尝试自己更改值。这可以通过
boolean
字段来完成,例如代码中的
isSeeking
字段。然而,与您的代码不同的是,我们不能根据
isSeeking
属性的值设置
valueChanging
,否则我们会遇到与之前相同的问题。需要在滑块值更改之前将其设置为 true。这可以通过事件filter 来完成。
progressSlider.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> isSeeking = true);
// set it back to false when the user is done changing the value
progressSlider.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> isSeeking = false);
根据媒体当前时间更新值您正在创建一个 Timer
Slider
和
ProgressBar
。这种方法有一些问题:您没有考虑用户当前是否正在寻找。
happens-before 关系。
使用后台线程是不必要的,并且会使事情变得更加复杂。您可以简单地向
Timer
currentTime
属性添加一个侦听器,并相应地更新
MediaPlayer
和
Slider
,而不是使用
ProgressBar
。
mediaPlayer.currentTimeProperty().addListener((obs, oldTime, newTime) -> {
if (!isSeeking) {
double value = newTime.toMillis() / mediaPlayer.getMedia().getDuration().toMillis();
// Assumes slider's min was set to 0.0 and its max to 1.0
progressSlider.setValue(value);
progressBar.setValue(value);
}
});
滑块或进度条我不完全确定为什么您同时使用 Slider
和
ProgressBar
来跟踪媒体播放器的当前时间。我建议您选择其中之一,而不是两者都选。
如果您出于某种视觉原因将它们组合在一起,我会首先看看您是否可以对其中一个进行样式设计,以仅使用其中一个来提供您想要的视觉效果。如果仅靠样式无法做到这一点,那么也许对其中一个皮肤进行子类化会有所帮助。如果做不到这一点,我可能只会制作一个完全自定义的控件。
示例
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private boolean isSeeking;
@Override
public void start(Stage primaryStage) {
// FIXME: Replace "..." with real media URI
var media = new Media("...");
var mediaPlayer = new MediaPlayer(media);
mediaPlayer.setAutoPlay(true);
var timeSlider = new Slider(0.0, 1.0, 0.0);
timeSlider.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> isSeeking = true);
timeSlider.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> isSeeking = false);
timeSlider.valueProperty().addListener(obs -> {
if (isSeeking) {
double millis = media.getDuration().toMillis() * timeSlider.getValue();
mediaPlayer.seek(Duration.millis(millis));
}
});
mediaPlayer.currentTimeProperty().addListener((obs, oldTime, newTime) -> {
if (!isSeeking) {
double value = newTime.toMillis() / media.getDuration().toMillis();
timeSlider.setValue(value);
}
});
var root = new StackPane(new MediaView(mediaPlayer), timeSlider);
StackPane.setAlignment(timeSlider, Pos.BOTTOM_CENTER);
StackPane.setMargin(timeSlider, new Insets(10));
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
}