同步媒体播放器javafx

时间:2017-02-23 08:30:03

标签: java javafx

这可能不是一个非常可接受的问题,但我现在非常绝望。

我需要一个快速搜索和平衡修改的同步java媒体播放器。

情景:

我有一个javaFX项目,我必须在一个循环中播放一个非常短(50-100毫秒)的媒体文件。问题是,我需要在重新启动之前保留。

简而言之: 播放声音 - >满足某些要求(平衡将被修改)(每120ms) - >如果声音从开始播放重播。

javafx提供了我修改过的媒体播放器。但需要更高的效率:

// this method is just to show how the modified mediaplayer class is called
public void updateSoundBalance(double currentTimeInCycle) {
    // control the balance of the sound
    if(playingSound && mediaPlayer != null)
    {
        long b = System.nanoTime();

        // 0 <= balance < 4. balance represents the cycle phase.
        double balance = currentTimeInCycle % RADIANCE_FULL_CYCLE / RADIANCE_QUARTER_CYCLE;
        boolean firstHalfCycle = balance < 2;

        double quarterCycleIndex = Math.floor(balance);

        long a = System.nanoTime();
        if(swingingSound)
            mediaPlayer.setBalance(firstHalfCycle ? 1 - balance : balance - 3);
        else
            mediaPlayer.setBalance(balance > 1 && balance < 3? -1 : 1);
        System.out.println("C   :::   sound balance = " + (System.nanoTime() - a));

        if ((quarterCycleIndex == 1 | quarterCycleIndex == 3) &&
            balance - quarterCycleIndex <= Settings.DEFAULT_PATTERN_SMOOTHNESS)
        {
            a = System.nanoTime();

            if (mediaDone){
                mediaPlayer.reset();
                mediaDone = false;
            }
            System.out.println("B   :::   call reset = " + (System.nanoTime() - a));
        }
        System.out.println("A   :::   total time = " + (System.nanoTime() - b));
    }
}
import java.util.concurrent.ScheduledThreadPoolExecutor;

import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;


public class MultiMediaPlayer
{
private MediaPlayer mp1, mp2;
private boolean usingMp1 = true;

private ScheduledThreadPoolExecutor seekService = new ScheduledThreadPoolExecutor(2);
private Runnable seekRun = new Runnable() {
    @Override
    public void run() {
        try
        {
            long a = System.nanoTime();
            if(usingMp1) {
                usingMp1 = false;
                mp1.stop();
                mp2.play();
                mp1.seek(new Duration(0));
            } else {
                usingMp1 = true;
                mp2.stop();
                mp1.play();
                mp2.seek(new Duration(0));
            }
            System.out.println("D   :::   reset sound time taken = " + (System.nanoTime() - a));
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
};

public MultiMediaPlayer(Media value)
{
    mp1 = new MediaPlayer(value);
    mp2 = new MediaPlayer(value);

    mp1.balanceProperty().bindBidirectional(mp2.balanceProperty());
    mp1.onEndOfMediaProperty().bindBidirectional(mp2.onEndOfMediaProperty());
}

public void setBalance(double value){
    mp1.setBalance(value);
}

public void reset(){
    seekService.execute(seekRun);
}

public void play(){
    if(usingMp1) {
        mp1.play();
    } else {
        mp2.play();
    }
}

public void stop(){
    mp1.stop();
    mp2.stop();
}

public void pause(){
    mp1.pause();
    mp2.pause();
}

public void setOnEndOfMedia(Runnable r) {
    mp1.setOnEndOfMedia(r);
}
}

如果有人能指出我正确的方向(图书馆/我错过的东西),我将不胜感激

ps允许的java版本是最新的

2 个答案:

答案 0 :(得分:0)

在这种情况下,您可以更好地使用更低级别的Java Sound API。它已经是标准API的一部分。你提到你的音频信号很短。因此,您可以将其缓冲在内存中,然后将其写入轮廓几次。 乍一看你的代码似乎pan可能是正确的选择,而不是平衡。以下示例分别演示了切换平衡和平移到其最大值和最小值。 Java Sound API本身适用于WAVE和AIFF。如果您有其他文件格式,则需要查看Java Sound API FormatConversionProviders,例如mp3spi和vorbisspi。

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Main {

    public static void main(String[] args) {

        try {
            int loops = 20;

            File waveFile = new File(yourWaveFilePath);

            AudioInputStream stream = AudioSystem.getAudioInputStream(waveFile);
            AudioFormat format = stream.getFormat();

            // reading complete audio file into memory
            byte[] frames = new byte[format.getFrameSize() * (int) stream.getFrameLength()];
            stream.read(frames, 0, frames.length);

            DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, format);
            SourceDataLine line = (SourceDataLine) AudioSystem.getLine(lineInfo);
            line.open(format);
            line.start();

            FloatControl balance = (FloatControl) line.getControl(FloatControl.Type.BALANCE);
            FloatControl pan = (FloatControl) line.getControl(FloatControl.Type.PAN);

            for (int i = 0; i < loops; i++) {
                // switching balance and pan with every iteration
                if (i % 2 == 0) {
                    balance.setValue(balance.getMinimum());
                    pan.setValue(pan.getMinimum());
                } else {
                    balance.setValue(balance.getMaximum());
                    pan.setValue(pan.getMaximum());
                }

                // playing complete audio file
                line.write(frames, 0, frames.length);

                System.out.println("iteration: " + i + ", balance: " + balance.getValue() + ", pan: " + pan.getValue());
            } 

            line.drain();
            line.close();
            stream.close();

        } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
            e.printStackTrace();
        }
    }

}

答案 1 :(得分:0)

感谢Olof Kohlhaas,但我认为我为我的具体问题找到了更好的解决方案,它使用了javafx的AudioClip。但是,由于我缺乏知识,我使用一种基本方法来获取仅支持Wave和其他特定格式的文件的长度。如果改变了,这个类适用于javafx的媒体包支持的任何格式:

import java.io.IOException;
import java.net.URL;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;

/**
 *
 * this class is to optimize sound pattern playing
 *
 * reasoning:
 * - audio clips are for short repetitive files that don't need live changes to balance
 * - media players are for long files that will have the ability to swing with the visual patted
 * by updating the balance whenever needed (every few updates)
 */
public class AudioPlayer
{
    public enum Controller{
        MEDIA_PLAYER, AUDIO_CLIP;
    }

    /**
     * this class is to help other classes keep track of this particular state of the AudioPlayer
     */
    public class ControllerProperty extends SimpleObjectProperty<Controller>{
        SimpleBooleanProperty isMediaPlayerProperty = new SimpleBooleanProperty();

        @Override
        public void set(Controller newValue) {
            super.set(newValue);
            if (newValue == Controller.MEDIA_PLAYER)
                isMediaPlayerProperty.set(true);
            else
                isMediaPlayerProperty.set(false);
        }

        public ReadOnlyBooleanProperty isMediaPlayerProperty() {
            return isMediaPlayerProperty;
        }
    }
    // different controllers used
    private Media media;
    private MediaPlayer mediaPlayer;
    private AudioClip audioClip;

    // controllerProperty property indicator
    private ControllerProperty controllerProperty = new ControllerProperty();

    private boolean mediaDonePlaying = true;
    private double durationMillis;

    /**
     * Constructor. This will be the place where you can
     * @param srcUrl
     */
    public AudioPlayer(String srcUrl) {
        boolean formatSupported = true;
        try {
            durationMillis = getLength(srcUrl);
        } catch (IOException | LineUnavailableException e) {
            e.printStackTrace();
        } catch (UnsupportedAudioFileException e) {
            formatSupported = false;
        }
        // if file is long or format unsupported (not one of these: AudioSystem.getAudioFileTypes())
        if (durationMillis > 400 | !formatSupported){
            media = new Media(srcUrl);
            mediaPlayer = new MediaPlayer(media);
            controllerProperty.set(Controller.MEDIA_PLAYER);
            mediaPlayer.setOnEndOfMedia(() -> mediaDonePlaying = true);
        }
        else {
            audioClip = new AudioClip(srcUrl);
            controllerProperty.set(Controller.AUDIO_CLIP);
        }
    }

    /**
     * gets the audio duration of the provided source in milliseconds
     * @param path url string representation of the path
     * @return the length in milliseconds
     * @throws IOException
     * @throws UnsupportedAudioFileException
     * @throws LineUnavailableException
     */
    public static double getLength(String path) throws IOException, UnsupportedAudioFileException, LineUnavailableException
    {
        AudioInputStream stream;
        stream = AudioSystem.getAudioInputStream(new URL(path));
        AudioFormat format = stream.getFormat();
        if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
            format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format
                .getSampleRate(), format.getSampleSizeInBits() * 2, format
                .getChannels(), format.getFrameSize() * 2, format
                .getFrameRate(), true); // big endian
            stream = AudioSystem.getAudioInputStream(format, stream);
        }
        DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(),
            ((int) stream.getFrameLength() * format.getFrameSize()));
        Clip clip = (Clip) AudioSystem.getLine(info);
        clip.close();
        return clip.getBufferSize()
            / (clip.getFormat().getFrameSize() * clip.getFormat()
            .getFrameRate());
    }

    public void stop(){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    audioClip.stop();
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null){
                    mediaPlayer.stop();
                    mediaDonePlaying = true;
                }
                break;
        }
    }

    public void play(){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    if(!audioClip.isPlaying()){
                        audioClip.play();
                    }
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null){
                    mediaPlayer.play();
                    mediaDonePlaying = false;
                }
                break;
        }
    }

    public void pause(){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    audioClip.stop();
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null)
                    mediaPlayer.pause();
                break;
        }
    }

    /**
     * sets the balance of the player, if the controller is an {@link AudioClip}, the balance is 
     * updated at the next play cycle, if the controller is a {@link MediaPlayer} the balance is 
     * updated at the next time the {@link MediaPlayer} has Status.READY (read 
     * {@link MediaPlayer#setBalance(double)} for more details)
     * @param balance
     */
    public void setBalance(double balance){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    audioClip.setBalance(balance);
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null)
                    mediaPlayer.setBalance(balance);
                break;
        }
    }

    public String getSource(){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    return audioClip.getSource();
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null)
                    return media.getSource();
                break;
        }
        return null;
    }

    /**
     * @return if the file is done
     */
    public boolean isDonePlaying(){
        switch (controllerProperty.get())
        {
            case AUDIO_CLIP:
                if (audioClip != null)
                    return !audioClip.isPlaying();
                break;

            case MEDIA_PLAYER:
                if (mediaPlayer != null && media != null)
                    return mediaDonePlaying;
                break;
        }
        throw new IllegalStateException("Internal Error");
    }

    public ControllerProperty controllerProperty() {
        return controllerProperty;
    }
}