山东大学软件学院面向对象设计技术课程的实验之一。(说起来这些实验感觉没有什么太难的地方,基本没有什么实用性。)。(还有一个感受,重修太痛苦,别人两个人写一个,我要一个人写两个。好像我也算不上重修)
模拟实现一个多功能媒体播放器,它能播放音频(如mp3歌曲)、视频(选作,不属于基本要求)。媒体播放器界面,有进度条,可展示总播放时间、当前播放时间,进度条可拖动,有播放、暂停键,有快进、快退键。
这个实验题目很明确,就是放音乐,网上一搜,java播放音乐的代码有的是,视频也有。
先上效果图:
(最近迷上了花花,全都是花花的歌。。。)
个人感觉完成度比较高吧。
实现的功能:音乐播放、上一首、下一首、随机播放、单曲循环、实时歌词、视频播放。
首先是解耦的过程。先要明确设计好哪些模块。我是按照 内容 -> 样式 -> 行为 的思路写的(基本是按照html -> css -> js的思路)。
不过,内容和样式是一起的而已。
第一部分:展示当前播放音频的文件名
第二部分:展示文件列表,包括视频和音频
第三部分:展示歌词信息
第四部分:控制部分,包含各种按钮、进度条等实现播放控制、模式控制等。
显然,视频播放不能在主界面实现,所以需要额外添加一个窗口播放视频,基本的设计与上图相比,仅保留第三、四部分。
我们都知道,java swing的基本组件都很难看,好在可以通过SliderUI来重新设计自己的UI。下面是我的:
package UI;
import Value.PlayerColor; //自己写的类,其中定义了所有的颜色常量
import javax.swing.*;
import javax.swing.plaf.basic.BasicSliderUI;
import java.awt.*;
public class SliderUI extends BasicSliderUI {
public SliderUI(JSlider b) {
super(b);
}
@Override
public void paintThumb(Graphics g) {
//绘制游标
Graphics2D g2d = (Graphics2D) g;
BasicStroke stroke = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
g2d.setStroke(stroke);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
GradientPaint gp = new GradientPaint(0, 0, PlayerColor.Default, 0, thumbRect.height, PlayerColor.Default);
g2d.setPaint(gp);
g2d.fillOval(thumbRect.x, thumbRect.y + 5, 10, 10);
BasicStroke stroke1 = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
g2d.setStroke(stroke1);
g2d.drawLine(0, thumbRect.height / 2, thumbRect.x + 8, thumbRect.height / 2);
}
@Override
public void paintTrack(Graphics g) {
//绘制滑道
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);// 设定渐变
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
g2d.setPaint(new GradientPaint(0, 0, PlayerColor.SliderStart, 0, trackRect.height, PlayerColor.SliderEnd, true));
g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.drawLine(8, trackRect.height / 2 + 1, trackRect.width + 8, trackRect.height / 2 + 1);
}
}
实现音乐列表和视频列表的切换。首先,这个不能用基本的JPanel,否则多余的列表项就不会现实出来,得用滑动面板JScrollPane。关于这个JScrollPane的用法,我也是查了好多博客才弄明白,需要注意一下几点:
scrollPane = new JScrollPane();
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);//设置水平条不出现
scrollPane.getVerticalScrollBar().setUI(new ScrollBarUI());//重新UI,类似于2.2
scrollPane.setBounds(0, 100, 300, 600);//设置本身大小
scrollPane.setViewportView(asideList);
/*
asideList是面板,用于容纳每一项,在构造asideList的时候必须有下面的语句,num是列表项的数目:
setVisible(true);
setPreferredSize(new Dimension(300, 50 * num));
*/
scrollPane.setBorder(null);
add(scrollPane);
UI
package UI;
import Value.PlayerColor; //自己写的类,其中定义了所有的颜色常量
import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.plaf.basic.BasicScrollBarUI;
public class ScrollBarUI extends BasicScrollBarUI {
@Override
protected void configureScrollBarColors() {
trackColor = PlayerColor.ListEvenItemBackground;
setThumbBounds(0, 0, 10, 10);
}
@Override
public Dimension getPreferredSize(JComponent c) {
c.setPreferredSize(new Dimension(5, 0));
return super.getPreferredSize(c);
}
@Override
public void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = null;
if (this.scrollbar.getOrientation() == JScrollBar.VERTICAL)
gp = new GradientPaint(0, 0, PlayerColor.ListEvenItemBackground, trackBounds.width, 0, PlayerColor.ListEvenItemBackground);
g2.setPaint(gp);
g2.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);//填充Track
if (trackHighlight == BasicScrollBarUI.DECREASE_HIGHLIGHT)
this.paintDecreaseHighlight(g);
if (trackHighlight == BasicScrollBarUI.INCREASE_HIGHLIGHT)
this.paintIncreaseHighlight(g);
}
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
g.translate(thumbBounds.x, thumbBounds.y);// 这句一定一定要加上,不然拖动就失效了
g.setColor(PlayerColor.LyricsBackground);// 设置把手颜色
g.drawRoundRect(0, 0, 5, thumbBounds.height - 1, 5, 5);
// 消除锯齿
Graphics2D g2 = (Graphics2D) g;
RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.addRenderingHints(rh);
// 半透明
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2.fillRoundRect(0, 0, 40, thumbBounds.height - 1, 5, 5);
}
@Override
protected JButton createIncreaseButton(int orientation) {
JButton button = new JButton();
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setBorder(null);
return button;
}
@Override
protected JButton createDecreaseButton(int orientation) {
JButton button = new JButton();
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setFocusable(false);
button.setBorder(null);
return button;
}
}
实现mp3播放的有很多方式,我用的是下面这个:
<dependency>
<groupId>com.googlecode.soundlibsgroupId>
<artifactId>mp3spiartifactId>
<version>1.9.5.4version>
dependency>
使用方式:
File file = new File(path);
FileInputStream stream = new FileInputStream(file);
Player player = new Player(stream);
player.play();
但是直接这样子是有问题的。播放之后会阻塞,所以需要新开辟一个线程
上一首、下一首、随机播放、循环播放等,没有什么难点。就是设置几个变量,在一首音乐播放完毕后,检查这几个变量就行。
没有控制播放进度的功能,我的解决方法是讲音频文件变成字节流,在控制时再将字节流转换成文件流
使用lrc文件,可以看出很好处理,前面是时间,后面是歌词,单独组织称一个数据结构保存,开辟一个线程控制。
视频的实现是用VLC。安装VLC时先看看本身能不能正常播放(我下了一个都不能正常播放,更不用说用代码控制了)
安装好之后发现基本的播放功能VLC都是提供的。而且只要字幕(弹幕)文件和视频放在同一个文件夹下就能自动加载进去,如下图
此处是源码