2016-03-06 177 views
0

我創建了一個線程,通過將其轉換爲一個字節數組來播放Java文件中的mp3文件。如何跟蹤音頻播放位置?

我想知道如果我可以跟蹤當前的播放位置,因爲MP3正在播放。

首先,我建立了我的音樂流像這樣:

try { 
     AudioInputStream in = AudioSystem.getAudioInputStream(file); 

     musicInputStream = AudioSystem.getAudioInputStream(MUSIC_FORMAT, in); 

     DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, MUSIC_FORMAT); 
     musicDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); 
     musicDataLine.open(MUSIC_FORMAT); 
     musicDataLine.start();    
     startMusicThread(); 
    } catch(Exception e) { 
     e.printStackTrace(); 
    } 

接下來,我的音樂主題是這樣的:

private class MusicThread extends Thread {  
    byte musicBuffer[] = new byte[BUFFER_SIZE]; 
    public void run() { 
     try { 
      int musicCount = 0; 
      while(writeOutput){ 
       if(writeMusic && (musicCount = musicInputStream.read(musicBuffer, 0, musicBuffer.length)) > 0){ 
        musicDataLine.write(musicBuffer, 0, musicCount); 
       } 
      } 
     } catch (Exception e) { 
      System.out.println("AudioStream Exception - Music Thread"+e); 
      e.printStackTrace(); 
     } 
    } 
} 

我想到了一個可能性,創建另一個用一個計時器進行線程,該計時器慢慢地逐漸減小,以顯示MP3歌曲的剩餘時間量。但這似乎不是一個好的解決方案。

回答

2

您的int musicCount(來自AudioInputStream.read(...)的返回值)告訴您讀取的字節數,因此您可以做一個小計算來確定您在流中的位置。 (DataLine有一些方法做一些數學題的你,但他們不能總是使用...見下文)。

int musicCount = 0; 
int totalBytes = 0; 

while (loop stuff) { 
    // accumulate it 
    // and do whatever you need with it 
    totalBytes += musicCount; 

    musicDataLine.write(...); 
} 

要獲得的秒數流逝,你可以做以下的事情:

AudioFormat fmt = musicInputStream.getFormat(); 

long framesRead = totalBytes/fmt.getFrameSize(); 
long totalFrames = musicInputStream.getFrameLength(); 

double totalSeconds = (double) totalFrames/fmt.getSampleRate(); 

double elapsedSeconds = 
    ((double) framesRead/(double) totalFrames) * totalSeconds; 

因此,您只需將每個循環的經過時間放在任何需要的地方即可。請注意,這種類型的準確性取決於緩衝區的大小。緩衝區越小,越準確。

此外,Clip有一些方法來爲你查詢這個(但你可能不得不改變你做了很多)。

這些方法(get(Long)FramePosition/getMicrosecondPosition)從DataLine繼承,所以你也可以打電話給他們在SourceDataLine,以及如果你不想自己做數學題。 但是,你基本上需要爲每一個你玩的文件換一行,所以這取決於你如何使用該行。 (我個人寧願只是做了自己的分工要求,因爲該行是一種不透明的。)


BTW:

musicDataLine.open(MUSIC_FORMAT); 

你應該打開你自己的緩衝區大小指定的路線,使用(AudioFormat, int)過載。 SourceDataLine.write(...)只有當內部緩衝區已滿時纔會阻止,所以如果它與您的字節數組大小不同,有時您的循環被阻塞,有時它只是旋轉。


MCVE的好措施:

SimplePlaybackProgress

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 
import java.io.*; 
import java.util.*; 
import javax.sound.sampled.*; 

public class SimplePlaybackProgress 
extends WindowAdapter implements Runnable, ActionListener { 
    class AudioPlayer extends Thread { 
     volatile boolean shouldPlay = true; 
     final int bufferSize; 

     final AudioFormat fmt; 

     final AudioInputStream audioIn; 
     final SourceDataLine audioOut; 

     final long frameSize; 
     final long totalFrames; 
     final double sampleRate; 

     AudioPlayer(File file) 
       throws UnsupportedAudioFileException, 
         IOException, 
         LineUnavailableException { 

      audioIn  = AudioSystem.getAudioInputStream(file); 
      fmt   = audioIn.getFormat(); 
      bufferSize = fmt.getFrameSize() * 8192; 
      frameSize = fmt.getFrameSize(); 
      totalFrames = audioIn.getFrameLength(); 
      sampleRate = fmt.getSampleRate(); 
      try { 
       audioOut = AudioSystem.getSourceDataLine(audioIn.getFormat()); 
       audioOut.open(fmt, bufferSize); 
      } catch (LineUnavailableException x) { 
       try { 
        audioIn.close(); 
       } catch(IOException suppressed) { 
        // Java 7+ 
        // x.addSuppressed(suppressed); 
       } 
       throw x; 
      } 
     } 

     @Override 
     public void run() { 
      final byte[] buffer = new byte[bufferSize]; 
      long framePosition = 0; 

      try { 
       audioOut.start(); 

       while (shouldPlay) { 
        int bytesRead = audioIn.read(buffer); 
        if (bytesRead < 0) { 
         break; 
        } 

        int bytesWritten = audioOut.write(buffer, 0, bytesRead); 
        if (bytesWritten != bytesRead) { 
         // shouldn't happen 
         throw new RuntimeException(String.format(
          "read: %d, wrote: %d", bytesWritten, bytesRead)); 
        } 

        framePosition += bytesRead/frameSize; 
        // or 
        // framePosition = audioOut.getLongFramePosition(); 
        updateProgressBar(framePosition); 
       } 

       audioOut.drain(); 
       audioOut.stop(); 
      } catch (Throwable x) { 
       showErrorMessage(x); 
      } finally { 
       updateProgressBar(0); 

       try { 
        audioIn.close(); 
       } catch (IOException x) { 
        showErrorMessage(x); 
       } 

       audioOut.close(); 
      } 
     } 

     void updateProgressBar(
       final long framePosition) { 
      SwingUtilities.invokeLater(new Runnable() { 
       @Override 
       public void run() { 
        double fractionalProgress = 
         (double) framePosition/(double) totalFrames; 
        int progressValue = (int) Math.round(
         fractionalProgress * theProgressBar.getMaximum()); 

        theProgressBar.setValue(progressValue); 

        int secondsElapsed = (int) Math.round(
         (double) framePosition/sampleRate); 
        int minutes = secondsElapsed/60; 
        int seconds = secondsElapsed % 60; 

        theProgressBar.setString(String.format(
         "%d:%02d", minutes, seconds)); 
       } 
      }); 
     } 

     void stopPlaybackAndDrain() throws InterruptedException { 
      shouldPlay = false; 
      this.join(); 
     } 
    } 

    /* * */ 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new SimplePlaybackProgress()); 
    } 

    JFrame theFrame; 
    JButton theButton; 
    JProgressBar theProgressBar; 

    // this should only ever have 1 thing in it... 
    // multithreaded code with poor behavior just bugs me, 
    // even for improbable cases, so the queue makes it more robust 
    final Queue<AudioPlayer> thePlayerQueue = new ArrayDeque<AudioPlayer>(); 

    @Override 
    public void run() { 
     theFrame = new JFrame("Playback Progress"); 
     theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     theButton = new JButton("Open"); 
     theProgressBar = new JProgressBar(
      SwingConstants.HORIZONTAL, 0, 1000); 
     theProgressBar.setStringPainted(true); 
     theProgressBar.setString("0:00"); 

     Container contentPane = theFrame.getContentPane(); 
     ((JPanel) contentPane).setBorder(
      BorderFactory.createEmptyBorder(8, 8, 8, 8)); 
     contentPane.add(theButton, BorderLayout.WEST); 
     contentPane.add(theProgressBar, BorderLayout.CENTER); 

     theFrame.pack(); 
     theFrame.setResizable(false); 
     theFrame.setLocationRelativeTo(null); 
     theFrame.setVisible(true); 

     theButton.addActionListener(this); 
     theFrame.addWindowListener(this); 
    } 

    @Override 
    public void actionPerformed(ActionEvent ae) { 
     JFileChooser dialog = new JFileChooser(); 
     int option = dialog.showOpenDialog(theFrame); 

     if (option == JFileChooser.APPROVE_OPTION) { 
      File file = dialog.getSelectedFile(); 
      try { 
       enqueueNewPlayer(new AudioPlayer(file)); 
      } catch (UnsupportedAudioFileException x) { // ew, Java 6 
       showErrorMessage(x);     // 
      } catch (IOException x) {     // 
       showErrorMessage(x);     // 
      } catch (LineUnavailableException x) {  // 
       showErrorMessage(x);     // 
      }           // 
     } 
    } 

    @Override 
    public void windowClosing(WindowEvent we) { 
     stopEverything(); 
    } 

    void enqueueNewPlayer(final AudioPlayer newPlayer) { 
     // stopPlaybackAndDrain calls join 
     // so we want to do it off the EDT 
     new Thread() { 
      @Override 
      public void run() { 
       synchronized (thePlayerQueue) { 
        stopEverything(); 
        newPlayer.start(); 
        thePlayerQueue.add(newPlayer); 
       } 
      } 
     }.start(); 
    } 

    void stopEverything() { 
     synchronized (thePlayerQueue) { 
      while (!thePlayerQueue.isEmpty()) { 
       try { 
        thePlayerQueue.remove().stopPlaybackAndDrain(); 
       } catch (InterruptedException x) { 
        // shouldn't happen 
        showErrorMessage(x); 
       } 
      } 
     } 
    } 

    void showErrorMessage(Throwable x) { 
     x.printStackTrace(System.out); 
     String errorMsg = String.format(
      "%s:%n\"%s\"", x.getClass().getSimpleName(), x.getMessage()); 
     JOptionPane.showMessageDialog(theFrame, errorMsg); 
    } 
} 

對於Clip,你就必須像一個Swing計時器(或另一側線程),並常常但查詢它:

new javax.swing.Timer(100, new ActionListener() { 
    @Override 
    public void actionPerformed(ActionEvent ae) { 
     long usPosition = theClip.getMicrosecondPosition(); 
     // put it somewhere 
    } 
}).start(); 

相關:

+1

@WayWay我知道你已經接受了我的答案,但我還添加了一個MCVE,平時我喜歡做(謝謝!)對於這種類型的問題。我昨天沒有太多時間。 – Radiodef

+0

非常感謝!我一定會用它作爲良好編碼風格的參考:) – wayway