今天上班实在是无法静下心来工作,突然想到给我以前写的mp3播放器添加一个歌词显示组件.
大概思路是这样.
在一个组件上绘制出所有的歌词信息.
然后按照歌曲进度和歌词所在的进度移动示口,把要显示的那行歌词显示在组件中央.
如果必要,可以重绘组件屏幕中央的那行文字.加上一些效果.(现在没有做)
而且也没有做offset的修正.
现在实在是太简陋了,等有空再完善.要做的事实在太多了.
那个ProgressListener在我以前的文章<swing做的mp3播放器>中.
首先是lrc模型,歌词的内容保存在TreeMap中,key为歌词开始的时间.单位为毫秒.value为歌词.
package jmp123.shell.lrc;
import java.util.TreeMap;
class LRC {
private String artist;
private String title;
private String album;
private String maker;
private int offset;
private TreeMap<Integer, String> texts;
public String getArtist() {
return this.artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAlbum() {
return this.album;
}
public void setAlbum(String album) {
this.album = album;
}
public String getMaker() {
return this.maker;
}
public void setMaker(String maker) {
this.maker = maker;
}
public int getOffset() {
return this.offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public TreeMap<Integer, String> getTexts() {
if (this.texts == null) {
this.texts = new TreeMap<Integer, String>();
}
return this.texts;
}
}
接着是读取和显示组件:
package jmp123.shell.lrc;
import java.awt.Dimension;
/**
* 读取歌词 <br>
*
* @日期. 2010-10-9 下午01:27:49
*/
public class LRCReader extends JScrollPane {
private class DisplayComponent extends JComponent {
/**
*
*/
private static final long serialVersionUID = -1926347751983144607L;
@Override
protected void paintComponent(Graphics g) {
int width = LRCReader.this.getViewport().getWidth();
int height = this.getStringHeight();
int y = (LRCReader.this.getViewport().getHeight() - height) / 2;
String title = "标题: " + LRCReader.this.lrc.getTitle();
g.drawString(title, (width - this.getStringWidth(title)) / 2, y);
y += height;
String artist = "艺术家: " + LRCReader.this.lrc.getArtist();
g.drawString(artist, (width - this.getStringWidth(artist)) / 2, y);
y += height;
String album = "专辑: " + LRCReader.this.lrc.getAlbum();
if (album != null) {
g
.drawString(album,
(width - this.getStringWidth(album)) / 2, y);
}
y += height;
String maker = LRCReader.this.lrc.getMaker();
if (maker != null) {
maker = "By: " + maker;
g
.drawString(maker,
(width - this.getStringWidth(maker)) / 2, y);
}
y += height;
// 空一行
y += height;
LRCReader.this.raise = height * 5 * 1000
/ LRCReader.this.lrc.getTexts().firstKey();
// 内容
for (String s : LRCReader.this.lrc.getTexts().values()) {
g.drawString(s, (width - this.getStringWidth(s)) / 2, y);
y += height;
}
if (this.getHeight() < y) {
this.setPreferredSize(new Dimension(this.getWidth(), y));
}
}
private int getStringWidth(String text) {
Graphics g = this.getGraphics();
FontMetrics fm = g.getFontMetrics();
int w = 0;
for (char c : text.toCharArray()) {
w += fm.charWidth(c);
}
return w;
}
private int getStringHeight() {
Graphics g = this.getGraphics();
FontMetrics fm = g.getFontMetrics();
return fm.getAscent() + fm.getDescent() + fm.getLeading();
}
public void update(int current) {
Integer key = LRCReader.this.lrc.getTexts().floorKey(current);
System.out.println("Current:" + current + ";Key:" + key);
if (key != null) {
if (key != LRCReader.this.lrc.getTexts().firstKey()) {
Integer next = LRCReader.this.lrc.getTexts().higherKey(key);
if (next != null) {
int i = next - key;
if (i > 0) {
LRCReader.this.raise = this.getStringHeight()
* 1000 / i;
System.out.println(raise);
}
}
}
}
}
}
/**
*
*/
private static final long serialVersionUID = 7690089125689655670L;
/**
* artist艺术家、演唱者
*/
private static final String TAG_AR = "ar";
/**
* title题目,标题,曲目
*/
private static final String TAG_TI = "ti";
/**
* album唱片集、专辑
*/
private static final String TAG_AL = "al";
/**
* (介词)制作者、编辑人员(一般指lrc歌词的制作人)
*/
private static final String TAG_BY = "by";
/**
* 其单位是毫秒,正值表示延迟,负值表示提前。用于整体调整歌词显示慢(快)于音乐播放。
*/
private static final String TAG_OS = "offset";
/**
* lrc文件路径
*/
private String path;
/**
* lrc内容
*/
private LRC lrc;
private ProgressListener listener;
private volatile int raise;
/**
* 匹配标签
*/
private static Pattern PATTER_TAG = Pattern
.compile("\\[(.*?)\\]");
/**
* 匹配时间
*/
private static Pattern PATTER_TIME = Pattern
.compile("(\\d+):(\\d+)(\\.(\\d+))?");
public LRCReader(String path) {
this.path = path;
this
.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
this
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
this.init();
}
private void init() {
if (this.path != null) {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(this.path),
"GBK"));
String line = null;
this.lrc = new LRC();
while ((line = reader.readLine()) != null) {
if (line.trim().length() > 0) {
Matcher tagMatcher = LRCReader.PATTER_TAG.matcher(line);
while (tagMatcher.find()) {
String tag = tagMatcher.group(1).trim();
Matcher timeMatcher = LRCReader.PATTER_TIME
.matcher(tag);
if (timeMatcher.matches()) {// lrc内容
String m = timeMatcher.group(1);
String s = timeMatcher.group(2);
String ms = timeMatcher.group(4);
int tms = 0;
tms += Integer.valueOf(m) * 60 * 1000;
tms += Integer.valueOf(s) * 1000;
if (ms != null) {
tms += Integer.valueOf(ms);
}
String text = line.substring(
line.lastIndexOf("]") + 1).trim();
this.lrc.getTexts().put(tms, text);
} else {// tags
if (tag != null) {
String[] array = tag.split(":");
String key = array[0].trim();
String value = array[1].trim();
if (LRCReader.TAG_AL.equalsIgnoreCase(key)) {
this.lrc.setAlbum(value);
} else if (LRCReader.TAG_AR
.equalsIgnoreCase(key)) {
this.lrc.setArtist(value);
} else if (LRCReader.TAG_BY
.equalsIgnoreCase(key)) {
this.lrc.setMaker(value);
} else if (LRCReader.TAG_OS
.equalsIgnoreCase(key)) {
this.lrc.setOffset(Integer
.valueOf(value));
} else if (LRCReader.TAG_TI
.equalsIgnoreCase(key)) {
this.lrc.setTitle(value);
}
}
}
}
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.lrc != null) {
this.setViewportView(new DisplayComponent());
final Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
JViewport vp = LRCReader.this.getViewport();
Point p = vp.getViewPosition();
p.setLocation(p.x, p.y + LRCReader.this.raise);
if (p.y < vp.getView().getHeight() - vp.getHeight() / 2) {
vp.setViewPosition(p);
}
}
});
timer.start();
}
}
public ProgressListener getLRCListener() {
if (this.listener == null) {
this.listener = new ProgressListener() {
public void update(ProgressEvent event) {
int current = (int) (event.getLength() * 1000
* event.getFrame() / event.getFrames());
((DisplayComponent) LRCReader.this.getViewport().getView())
.update(current);
}
};
}
return this.listener;
}
public static void main(String[] args) {
LRCReader r = new LRCReader("E:\\music\\lyrics\\王力宏 - 十八般武艺_1.lrc");
LRC lrc = r.lrc;
System.out.println(lrc.getArtist());
for (Map.Entry<Integer, String> entry : lrc.getTexts().entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
测试类:
package jmp123.shell.lrc;
import java.awt.BorderLayout;
public class LRCTest extends JFrame {
/**
*
*/
private static final long serialVersionUID = -9185495379210075085L;
private JPanel contentPane;
private LRCReader reader;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
LRCTest frame = new LRCTest();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public LRCTest() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setBounds(100, 100, 450, 431);
this.contentPane = new JPanel();
this.contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
this.contentPane.setLayout(new BorderLayout(0, 0));
this.setContentPane(this.contentPane);
this.contentPane.add(this.getReader(), BorderLayout.CENTER);
}
private LRCReader getReader() {
if (this.reader == null) {
this.reader = new LRCReader("E:\\music\\lyrics\\王力宏 - 十八般武艺_1.lrc");
final ProgressListener pl = this.reader.getLRCListener();
new Timer(1000, new ActionListener() {
private int i = 0;
public void actionPerformed(ActionEvent e) {
pl.update(new ProgressEvent(null, 240, 240, i++));
}
}).start();
}
return this.reader;
}
}