如何修复进度条和滑块以在 JavaFX MediaPlayer 中的任何所需位置进行搜索

问题描述 投票:0回答:1

我正在使用 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。但这不起作用。我一定错过了一些东西,但不知道是什么。

javafx fxml
1个回答
0
投票

修复/改进

我注意到您的代码中有一些可以改进的地方。

寻找

根据您当前的代码,我假设以下内容:

  1. 如果拖动

    Slider
    的拇指,则可以进行查找。

  2. 但是,如果单击

    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
。这种方法有一些问题:

您没有考虑用户当前是否正在寻找。
  1. 您正在从后台线程读取由
  2. JavaFX 应用程序线程
  3. 更新的状态,而两个线程之间没有建立任何显式的

    happens-before 关系。

    使用后台线程是不必要的,并且会使事情变得更加复杂。
  4. 您可以简单地向
  5. 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(); } }


© www.soinside.com 2019 - 2024. All rights reserved.