首先我们先明确mp3有那些结构,MP3分为播放界面和播放列表界面,播放界面由暂停,上一首,下一首的按键,进度条和背景组成。播放列表界面由音乐的列表,上传和存入列表的按钮组成。下面是演示结果:

我们创建一个界面用jpanle画布将其分为左右两侧,以下是界面的创建和按钮的实现:

public void showUI(){
        JFrame mp3=new JFrame("mp3V2.0");
        mp3.setSize(1000,600);
        mp3.setLocationRelativeTo(null);//居中
        mp3.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //操作面
        JPanel music=new JPanel();
        music.setBackground(Color.gray);
        music.setPreferredSize(new Dimension(500,0));
        music.setLayout(new FlowLayout());//流式布局
        mp3.add(music,BorderLayout.WEST);//将画布添加至界面左侧
        //刷新面的按钮添加
        JButton load=new JButton("刷新列表");
        JButton mange=new JButton("上传音频");
        //创建列表
        String[] title={"文件名称","文件地址"};
        String[][] musicFileList={};
        JTable fileList=new JTable(new DefaultTableModel(musicFileList,title));
        fileList.setBackground(Color.yellow);
        //这里只是设置了表格,还要用JScrollPane来显示滚轮
        JScrollPane showTable=new JScrollPane(fileList);
        showTable.setPreferredSize(new Dimension(500,500));
        //按顺序添加组件
        music.add(showTable);
        music.add(load);
        music.add(mange);

        //播放界面
        PlayerPanel player=new PlayerPanel(true);
        player.setPreferredSize(new Dimension(500,0));
        player.setLayout(null);//不设置布局,直接固定组件
        JButton setBack = new JButton("设置背景");
        JButton choose = new JButton("选择音乐");
        JButton nextMusic = new JButton("下一首");
        JButton lastMusic = new JButton("上一首");
        //设置位置
        setBack.setBounds(395,0,90,25);
        choose.setBounds(190,530,120,25);
        nextMusic.setBounds(300,430,80,40);
        lastMusic.setBounds(120,430,80,40);
        //面板获取按钮
        player.getButton(setBack,choose,nextMusic,lastMusic);
        //添加按钮
        player.add(setBack);
        player.add(choose);
        player.add(nextMusic);
        player.add(lastMusic);
        mp3.add(player,BorderLayout.CENTER);

        mp3.setVisible(true);//显示界面
    }

其中PlayerPanle是继承JPanel的类,该类的实现如下:

public class PlayerPanel extends JPanel {
    //播放器界面
    private int w,h;
    public String backFilePath;//背景的文件路径
    public Image background;
    private boolean isPause;
    private boolean isLock=false;
    private int process=0;
    private int time;
    public JButton setBack,choose,nextMusic,lastMusic;

    //获取画笔和暂停键的状态
    public PlayerPanel(boolean isPause){
        this.isPause=isPause;
    }
    public void setIsLock(boolean isLock){
        this.isLock=isLock;
    }
    public boolean getIsLock(){
        return this.isLock;
    }
    public void setIsPause(boolean isPause){
        this.isPause=isPause;
    }
    public boolean getIsPause(){
        return this.isPause;
    }
    public void setTime(int time){
        this.time=time;
    }
    public int getTime(){
        return this.time;
    }
    public void setProcess(int pro){
        this.process=pro;
    }
    //获取背景路径
    public void getPath(String path){
        this.backFilePath=path;
        background=new ImageIcon(path).getImage();
    }

    //绘制背景板
    public void drawBack(Graphics g,Image img){
        if(img!=null){
            g.drawImage(img,0,0,null);
        }else{
            BufferedImage image=new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
            File file=new File("C:\\Users\\lenovo\\Desktop\\background\\C8A1A262D7A26D992AFBF878C064103D.jpg");
            try {
                BufferedImage img1 = ImageIO.read(file);
                Graphics imgG=image.getGraphics();
                int bit=Math.min(img1.getWidth()/w,1);
                bit=Math.min(img1.getHeight()/h,bit);
                for (int i = 0; i < w; i+=bit) {
                    for (int j = 0; j < h; j+=bit) {
                        imgG.setColor(new Color(img1.getRGB(i,j)));
                        imgG.fillRect(i,j,1,1);
                    }
                }
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            g.drawImage(image,0,0,null);
        }
    }

    //绘制暂停键
    public void drawPause(boolean isPause,Graphics g){
        g.setColor(Color.white);
        g.fillOval(230,430,40,40);
        Graphics2D graphics=(Graphics2D) g;
        graphics.setStroke(new BasicStroke(5.0f));
        if(!isPause){//绘制播放键(||),isPause是false没有暂停
            graphics.setColor(Color.black);
            graphics.drawLine(245, 440, 245, 460);
            graphics.drawLine(255, 440, 255, 460);
            System.out.println("播放");
        }else {//绘制暂停键(|>)
            graphics.setColor(Color.black);
            graphics.drawLine(243, 440, 243, 460);
            graphics.drawLine(243, 440, 263, 450);
            graphics.drawLine(263, 450, 243, 460);
            System.out.println("暂停");
        }
    }

    //绘制进度条
    public void drawProcess(Graphics g,int pro){
        g.setColor(Color.white);
        g.drawRect(40,500,400,3);
        g.setColor(Color.black);
        g.fillRect(40, 500, pro, 4);
    }

    //获取按钮
    public void getButton(JButton setBack,JButton choose,JButton nextMusic,JButton lastMusic){
        this.setBack=setBack;
        this.choose=choose;
        this.nextMusic=nextMusic;
        this.lastMusic=lastMusic;
    }
    //重写刷新方式
    public void paint(Graphics g) {
        super.paint(g);
        Dimension dim1=this.getSize();
        w= dim1.width;
        h=dim1.height;
        drawBack(g,this.background);
        drawProcess(g,this.process);//先画进度条防止暂停键的Graphics2D转化影响
        drawPause(this.isPause,g);
        setBack.repaint();
        choose.repaint();
        nextMusic.repaint();
        lastMusic.repaint();
    }
}

我们通过固定的地址来存储文件目录,所以界面初始化时可以显示已上传的内容,实现方法如下:

public void getTable(String path, HashMap<String,String> map,JTable jt,JScrollPane js)
            throws IOException {//通过地址来给表格初始化
        File file=new File(path);//创建对应地址的文件
        BufferedReader bRead=new BufferedReader(new FileReader(file));//读取文件的文本内容
        String line=null;
        for (int i = 0; (line=bRead.readLine())!=null; i++) {//用变量获取一行文本内容
            String[] dataStr=line.split("#");//用一个数组存取一行的内容,以#来分隔
            if(dataStr.length<2){continue;}
            String id=dataStr[0];
            String way=dataStr[1];
            DefaultTableModel model=(DefaultTableModel) jt.getModel();//获取表格的模板
            String[][] row={{id,way}};
            map.put(id,way);
            model.addRow(row[0]);
            jt.updateUI();
            js.setViewportView(jt);
        }
    }

再在界面实现中添加方法:

//用创建的方法来实现表格
        HashMap<String,String> musicMap=new HashMap<>();
        try {
            getTable("C:\\Users\\lenovo\\IdeaProjects\\untitled\\src\\com11\\Music Library.txt",musicMap,fileList,showTable);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

接下来就是实现按钮的功能,先创建一个监听器:

public class ButtonListener implements ActionListener {
    
   private JTable table;
    private HashMap<String,String> musicMap;
    private PlayerPanel panel;
    private int loadMusicNum=0;
    private int point=0;//指向第几首歌
    private Lock lock;
    private Condition stopMusic;
    private Condition stopLine;

    public ButtonListener(JTable table, HashMap<String,String> hashmap, PlayerPanel panel, Lock lock,Condition stopMusic,Condition stopLine){
        this.lock=lock;
        this.stopMusic=stopMusic;
        this.stopLine=stopLine;
        this.panel =panel;
        this.musicMap=hashmap;
        this.table=table;
    }
    

    public void actionPerformed(ActionEvent e) {
        String bunStr=e.getActionCommand();//获取按钮文本
        if(bunStr.equals("刷新列表")){}
        if(bunStr.equals("上传音频")){}
        if(bunStr.equals("设置背景")){}
        if(bunStr.equals("选择音乐")){}
        if(bunStr.equals("下一首")){}
        if(bunStr.equals("上一首")){}
    }
}

重写它的构造方法,获取想要的内容,通过获取按钮文本,识别想要的功能。

接着编写每个按钮的功能,先实现上传音频:

if(bunStr.equals("上传音频")){
            load(this.table);
        }
//编写方法封装
    private void load(JTable table){
        JFileChooser jfc=new JFileChooser();
        jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);//只选择文件夹
        int state=jfc.showDialog(null,"选择文件夹");//选择按钮的文本,显示文件选择器,返回值为int
        if(state==JFileChooser.APPROVE_OPTION){//通过比对返回值,来识别是否进行以下操作
            //获取文件夹中的文件,如果是音频文件就放在表格中
            File file=jfc.getSelectedFile();
            DefaultTableModel model=(DefaultTableModel) table.getModel();
            for(File file1:file.listFiles()){
                if(file1.isFile()){
                    if(file1.getName().endsWith(".wav")){
                        String[][] str= {{file1.getName(), file1.getAbsolutePath()}};
                        model.addRow(str[0]);
                        loadMusicNum++;//记录添加的个数
                    }
                }
            }
            table.updateUI();//刷新列表,显示添加后的列表
        }
    }
if(bunStr.equals("刷新列表")){
            try {
                renewList("C:\\Users\\lenovo\\IdeaProjects\\untitled\\src\\com11\\Music Library.txt",musicMap,table);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

private void renewList(String path, HashMap<String,String> map, JTable jt) throws IOException {
        File file=new File(path);
        //获取文本的写入
        BufferedWriter bfWriter=new BufferedWriter(new FileWriter(file));
        for(int i=0;i<loadMusicNum;i++){
            String id=(String) jt.getModel().getValueAt(i,0);//object要进行强制类型转化
            String strPath=(String) jt.getModel().getValueAt(i,1);
            bfWriter.write(id+"#"+strPath+"\r\n");//将列表中获取的新文本覆盖原来的文本
            map.put(id,strPath);//将数据存入map中
        }
        bfWriter.flush();//将缓冲区的文本写入文件中
        bfWriter.close();//关闭写入流
    }
if(bunStr.equals("设置背景")){
            drawBack(panel);
        }

private void drawBack(PlayerPanel panel){
        JFileChooser jFileChooser=new JFileChooser();
        jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//只选择文件
        jFileChooser.setFileFilter(new FileNameExtensionFilter("JPG & PDF & PNG","jpg","pdf","png"));//选择文件的后缀
        int state=jFileChooser.showDialog(null,"选择背景图");
        if(state==JFileChooser.APPROVE_OPTION){
            String path=jFileChooser.getSelectedFile().getAbsolutePath();
            panel.getPath(path);
            panel.repaint();
        }
    }
if(bunStr.equals("选择音乐")){
            File choose=getFile();
        }

private File getFile(){
        JFileChooser jfc=new JFileChooser();
        jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        jfc.setFileFilter(new FileNameExtensionFilter("WAV","wav"));
        int state=jfc.showDialog(null,"选择音频");
        if(state==JFileChooser.APPROVE_OPTION){
            return jfc.getSelectedFile();
        }
        return null;
    }

好了通过按钮监听器我们获取到了wav的文件,下面就要来实现音频的播放了,这里我们可以使用Runnable类的接口来添加在线程中,启用线程实现音频的播放:

public class PlayerThread implements Runnable{

    private SourceDataLine output;//音频外部的输出
    private AudioInputStream input;//音频输入流
    private AudioFormat format;//音频的格式
    private Lock lock;
    private Condition stopMusic;
    private PlayerPanel panel;

    public PlayerThread(File file, PlayerPanel panel, Lock lock,Condition stopMusic) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        this.input=AudioSystem.getAudioInputStream(file);//获取音频文件的输入流
        this.format=input.getFormat();
        DataLine.Info streamFormat=new DataLine.Info(SourceDataLine.class,format);//获取一个输出通道,类型为外部设备输出,格式为输入流的格式
        this.output=(SourceDataLine) AudioSystem.getLine(streamFormat);//获得外部输出通道
        this.lock=lock;
        this.stopMusic=stopMusic;
        this.panel=panel;
    }
    public void run(){
        try {
            //先获取时间
            synchronized (panel){
                panel.setTime((int)(input.getFrameLength())/(int)(format.getFrameRate()));//总帧数÷每秒帧数
            }
            output.open(format);//打开通道,准备硬件
            output.start();//开启外部设备,不写就听不见声音
            byte[] abData=new byte[512];//缓存区
            int nBytesRead;
            while ((nBytesRead = input.read(abData, 0, abData.length))>=0) {//识别是否将内容获取完
                lock.lock();
                try {
                    while (panel.getIsPause()) {
                        panel.setIsLock(true);
                        stopMusic.await();
                    }
                    output.write(abData, 0, nBytesRead);//外部设备输出
                }finally {
                    lock.unlock();
                }
            }
            output.flush();//将缓冲区的内容全部输出
            output.close();//关闭通道
            panel.setIsPause(true);
        } catch (LineUnavailableException | InterruptedException | IOException e) {
            throw new RuntimeException(e);
        }
    }
}

还可以在编写一个进度条的线程接口:

public class LineThread implements Runnable {
    private PlayerPanel panel;
    private Lock lock;
    private Condition stopLine;
    private int pro = 0;
    private Graphics g;

    public LineThread(PlayerPanel panel, Lock lock, Condition stopLine) {
        this.panel = panel;
        this.lock = lock;
        this.stopLine = stopLine;
        this.g = panel.getGraphics();
    }

    public void run() {
        while (pro <= 400) {
            try {
                Thread.sleep((panel.getTime()*1000)/400);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            lock.lock();
            try {
                while (panel.getIsPause()) {
                    stopLine.await();
                }
                g.setColor(Color.black);
                g.fillRect(40 + pro, 500, 1, 4);
                pro++;
                panel.setProcess(pro);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
        panel.setProcess(0);
    }
}

现在可以将按钮监听器中,点击“选择音乐”的操作进行修改:

if(bunStr.equals("选择音乐")) {
            File choose = getFile();
            try {
                panel.setIsPause(false);
                PlayerThread playerThread = new PlayerThread(choose, panel, lock, stopMusic);
                LineThread lineThread = new LineThread(panel, lock, stopLine);
                new Thread(playerThread).start();
                new Thread(lineThread).start();
                panel.repaint();
            } catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
                throw new RuntimeException(ex);
            }
        }

最后将监听器添加在按钮和界面中就可以实现基础的播放了:

Lock lock=new ReentrantLock();
        Condition stopMusic=lock.newCondition();
        Condition stopLine=lock.newCondition();//对应两个线程
        ButtonListener btl=new ButtonListener(fileList,musicMap,player,lock,stopMusic,stopLine);
        load.addActionListener(btl);
        mange.addActionListener(btl);
        setBack.addActionListener(btl);
        choose.addActionListener(btl);
        nextMusic.addActionListener(btl);
        lastMusic.addActionListener(btl);
        PauseListener pauseListener =new PauseListener(player,stopMusic,stopLine,lock);
        player.addMouseMotionListener(pauseListener);
        player.addMouseListener(pauseListener);

现在我们就还差暂停的实现和上下首的选择。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐