千里冰封
JAVA 浓香四溢
posts - 151,comments - 2801,trackbacks - 0

在最开始写netbeans插件的时候,就已经开始在想如何实现同步的歌词显示,并且当时也差不多实现了大概的框架,所以YOYOPlayer的歌词显示模块基本上和netbeans插件的歌词显示模块是一样的,只不过一些细节做了一些改进,比如每行歌词的渐入渐出,以后单行歌词实现的卡拉OK效果等等,并把一些设置集成到了整个YOYOPlayer的设置里面去了.

在我实现歌词同步显示的时候,思路是把每首歌的歌词分成一句一句,并且为每一句歌词定义一个对象,Sentence,它代表歌词里面的每一句,那么整首歌的对象呢?整首歌的对象我定了一个Lyric对象,它代表的是一首歌的歌词.它里面也包括了自动搜索歌词的实现,当然歌词有了得让它显示出来啊,所以又定义了一个专门用来显示歌词的面板,LyricPanel,它是继承自JPanel,为了统一管理,加上边框,所以又加了一个LyricUI,这个类是直接放置到歌词显示窗口的,LyricPanel被放置在LyricUI里面,这个时候LyricUI设置一下Border,就实现了无修饰窗口的有修饰效果,至于这一点,我们以后再讲.

各部份的代码如下:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package com.hadeslee.yoyoplayer.lyric;

import com.hadeslee.yoyoplayer.playlist.PlayListItem;
import com.hadeslee.yoyoplayer.util.Config;
import com.hadeslee.yoyoplayer.util.Util;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 表示一首歌的歌词对象,它可以以某种方式来画自己
 * 
@author hadeslee
 
*/
public class Lyric implements Serializable {

    
private static final long serialVersionUID = 20071125L;
    
private static Logger log = Logger.getLogger(Lyric.class.getName());
    
private int width;//表示歌词的显示区域的宽度
    private int height;//表示歌词的显示区域的高度
    private long time;//表示当前的时间是多少了。以毫秒为单位
    private long tempTime;//表示一个暂时的时间,用于拖动的时候,确定应该到哪了
    private List<Sentence> list = new ArrayList<Sentence>();//里面装的是所有的句子
    private boolean isMoving;//是否正在被拖动 
    private int currentIndex;//当前正在显示的歌词的下标
    private boolean initDone;//是否初始化完毕了
    private transient PlayListItem info;//有关于这首歌的信息
    private transient File file;//该歌词所存在文件
    private boolean enabled = true;//是否起用了该对象,默认是起用的
    private long during = Integer.MAX_VALUE;//这首歌的长度
    /**
     * 用ID3V1标签的字节和歌名来初始化歌词
     * 歌词将自动在本地或者网络上搜索相关的歌词并建立关联
     * 本地搜索将硬编码为user.home文件夹下面的Lyrics文件夹
     * 以后改为可以手动设置.
     * 
@param songName 歌名
     * 
@param data ID3V1的数据
     
*/
    
public Lyric(final PlayListItem info) {
        
this.info = info;
        
this.during = info.getLength() * 1000;
        
this.file = info.getLyricFile();
        log.info(
"传进来的歌名是:" + info.toString());
        
//只要有关联好了的,就不用搜索了直接用就是了
        if (file != null) {
            log.log(Level.INFO, 
"不用找了,直接关联到的歌词是:" + file);
            init(file);
            initDone 
= true;
            
return;
        } 
else {
            
//否则就起一个线程去找了,先是本地找,然后再是网络上找
            new Thread() {

                
public void run() {
                    doInit(info);
                    initDone 
= true;
                }
            }.start();
        }

    }

    
/**
     * 读取某个指定的歌词文件,这个构造函数一般用于
     * 拖放歌词文件到歌词窗口时调用的,拖放以后,两个自动关联
     * 
@param file 歌词文件
     * 
@param info 歌曲信息
     
*/
    
public Lyric(File file, PlayListItem info) {
        
this.file = file;
        
this.info = info;
        init(file);
        initDone 
= true;
    }

    
/**
     * 根据歌词内容和播放项构造一个
     * 歌词对象
     * 
@param lyric 歌词内容
     * 
@param info 播放项
     
*/
    
public Lyric(String lyric, PlayListItem info) {
        
this.info = info;
        
this.init(lyric);
        initDone 
= true;
    }

    
private void doInit(PlayListItem info) {
        init(info);

        Sentence temp 
= null;
        
//这个时候就要去网络上找了
        if (list.size() == 1) {
            temp 
= list.remove(0);
            
try {
                String lyric 
= Util.getLyric(info);
                
if (lyric != null) {
                    init(lyric);
                    saveLyric(lyric, info);
                } 
else {//如果网络也没有找到,就要加回去了
                    list.add(temp);
                }
            } 
catch (IOException ex) {
                Logger.getLogger(Lyric.
class.getName()).log(Level.SEVERE, null, ex);
                
//如果抛了任何异常,也要加回去了
                list.add(temp);
            }
        }
    }

    
/**
     * 把下载到的歌词保存起来,免得下次再去找
     * 
@param lyric 歌词内容
     * 
@param info 歌的信息
     
*/
    
private void saveLyric(String lyric, PlayListItem info) {
        
try {
            
//如果歌手不为空,则以歌手名+歌曲名为最好组合
            String name = info.getFormattedName() + ".lrc";
//            File dir = new File(Config.HOME, "Lyrics" + File.separator);
            File dir = Config.getConfig().getSaveLyricDir();
            dir.mkdirs();
            file 
= new File(dir, name);
            BufferedWriter bw 
= new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "GBK"));
            bw.write(lyric);
            bw.close();
            info.setLyricFile(file);
            log.info(
"保存完毕,保存在:" + file);
        } 
catch (Exception exe) {
            log.log(Level.SEVERE, 
"保存歌词出错", exe);
        }
    }

    
/**
     * 设置此歌词是否起用了,否则就不动了
     * 
@param b 是否起用
     
*/
    
public void setEnabled(boolean b) {
        
this.enabled = b;
    }

    
/**
     * 得到此歌词保存的地方
     * 
@return 文件
     
*/
    
public File getLyricFile() {
        
return file;
    }

    
/**
     * 调整整体的时间,比如歌词统一快多少
     * 或者歌词统一慢多少,为正说明要快,为负说明要慢
     * 
@param time 要调的时间,单位是毫秒
     
*/
    
public void adjustTime(int time) {
        
//如果是只有一个显示的,那就说明没有什么效对的意义了,直接返回
        if (list.size() == 1) {
            
return;
        }
        
for (Sentence s : list) {
            s.setFromTime(s.getFromTime() 
- time);
            s.setToTime(s.getToTime() 
- time);
        }
    }

    
/**
     * 根据一个文件夹,和一个歌曲的信息
     * 从本地搜到最匹配的歌词
     * 
@param dir 目录
     * 
@param info 歌曲信息 
     * 
@return 歌词文件
     
*/
    
private File getMathedLyricFile(File dir, PlayListItem info) {
        File matched 
= null;//已经匹配的文件
        File[] fs = dir.listFiles(new FileFilter() {

            
public boolean accept(File pathname) {
                
return pathname.getName().toLowerCase().endsWith(".lrc");
            }
        });
        
for (File f : fs) {
            
//全部匹配或者部分匹配都行
            if (matchAll(info, f) || matchSongName(info, f)) {
                matched 
= f;
                
break;
            }
        }
        
return matched;
    }

    
/**
     * 根据歌的信息去初始化,这个时候
     * 可能在本地找到歌词文件,也可能要去网络上搜索了
     * 
@param info 歌曲信息
     
*/
    
private void init(PlayListItem info) {
        File matched 
= null;
        
for (File dir : Config.getConfig().getSearchLyricDirs()) {
            log.log(Level.FINE, 
"正在搜索文件夹:" + dir);
            
//得到歌曲信息后,先本地搜索,先搜索HOME文件夹
            
//如果还不存在的话,那建一个目录,然后直接退出不管了
            if (!dir.exists()) {
                dir.mkdirs();
            }
            matched 
= getMathedLyricFile(dir, info);
            
//当搜索到了,就退出
            if (matched != null) {
                
break;
            }
        }
        log.info(
"找到的是:" + matched);
        
if (matched != null) {
            info.setLyricFile(matched);
            file 
= matched;
            init(matched);
        } 
else {
            init(
"");
        }
    }

    
/**
     * 根据文件来初始化
     * 
@param file 文件
     
*/
    
private void init(File file) {
        BufferedReader br 
= null;
        
try {
            br 
= new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
            StringBuilder sb 
= new StringBuilder();
            String temp 
= null;
            
while ((temp = br.readLine()) != null) {
                sb.append(temp).append(
"\n");
            }
            init(sb.toString());
        } 
catch (Exception ex) {
            Logger.getLogger(Lyric.
class.getName()).log(Level.SEVERE, null, ex);
        } 
finally {
            
try {
                br.close();
            } 
catch (Exception ex) {
                Logger.getLogger(Lyric.
class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    
/**
     * 是否完全匹配,完全匹配是指直接对应到ID3V1的标签,
     * 如果一样,则完全匹配了,完全匹配的LRC的文件格式是:
     * 阿木 - 有一种爱叫放手.lrc
     * 
@param info 歌曲信息
     * 
@param file 侯选文件
     * 
@return 是否合格
     
*/
    
private boolean matchAll(PlayListItem info, File file) {
        String name 
= info.getFormattedName();
        String fn 
= file.getName().substring(0, file.getName().lastIndexOf("."));
        
if (name.equals(fn)) {
            
return true;
        } 
else {
            
return false;
        }
    }

    
/**
     * 是否匹配了歌曲名
     * 
@param info 歌曲信息
     * 
@param file 侯选文件
     * 
@return 是否合格
     
*/
    
private boolean matchSongName(PlayListItem info, File file) {
        String name 
= info.getFormattedName();
        String rn 
= file.getName().substring(0, file.getName().lastIndexOf("."));
        
if (name.equalsIgnoreCase(rn) || info.getTitle().equalsIgnoreCase(rn)) {
            
return true;
        } 
else {
            
return false;
        }
    }

    
/**
     * 最重要的一个方法,它根据读到的歌词内容
     * 进行初始化,比如把歌词一句一句分开并计算好时间
     * 
@param content 歌词内容
     
*/
    
private void init(String content) {
        
//如果歌词的内容为空,则后面就不用执行了
        
//直接显示歌曲名就可以了
        if (content == null || content.trim().equals("")) {
            list.add(
new Sentence(info.getFormattedName(), Integer.MIN_VALUE, Integer.MAX_VALUE));
            
return;
        }
        
try {
            BufferedReader br 
= new BufferedReader(new StringReader(content));
            String temp 
= null;
            
while ((temp = br.readLine()) != null) {
                parseLine(temp.trim());
            }
            br.close();
            
//读进来以后就排序了
            Collections.sort(list, new Comparator<Sentence>() {

                
public int compare(Sentence o1, Sentence o2) {
                    
return (int) (o1.getFromTime() - o2.getFromTime());
                }
            });
            
//处理第一句歌词的起始情况,无论怎么样,加上歌名做为第一句歌词,并把它的
            
//结尾为真正第一句歌词的开始
            if (list.size() == 0) {
                list.add(
new Sentence(info.getFormattedName(), 0, Integer.MAX_VALUE));
                
return;
            } 
else {
                Sentence first 
= list.get(0);
                list.add(
0new Sentence(info.getFormattedName(), 0, first.getFromTime()));
            }

            
int size = list.size();
            
for (int i = 0; i < size; i++) {
                Sentence next 
= null;
                
if (i + 1 < size) {
                    next 
= list.get(i + 1);
                }
                Sentence now 
= list.get(i);
                
if (next != null) {
                    now.setToTime(next.getFromTime() 
- 1);
                }
            }
            
//如果就是没有怎么办,那就只显示一句歌名了
            if (list.size() == 1) {
                list.get(
0).setToTime(Integer.MAX_VALUE);
            } 
else {
                Sentence last 
= list.get(list.size() - 1);
                last.setToTime(info 
== null ? Integer.MAX_VALUE : info.getLength() * 1000 + 1000);
            }
        } 
catch (Exception ex) {
            Logger.getLogger(Lyric.
class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    
/**
     * 分析这一行的内容,根据这内容
     * 以及标签的数量生成若干个Sentence对象
     * 
@param line 这一行
     
*/
    
private void parseLine(String line) {
        
if (line.equals("")) {
            
return;
        }
        Matcher m 
= Pattern.compile("(?<=\\[).*?(?=\\])").matcher(line);
        List
<String> temp = new ArrayList<String>();
        
int length = 0;
        
while (m.find()) {
            String s 
= m.group();
            temp.add(s);
            length 
+= (s.length() + 2);
        }
        
try {
            String content 
= line.substring(length > line.length() ? line.length() : length);
            
if (Config.getConfig().isCutBlankChars()) {
                content 
= content.trim();
            }
            
if (content.equals("")) {
                
return;
            }
            
for (String s : temp) {
                
long t = parseTime(s);
                
if (t != -1) {
                    list.add(
new Sentence(content, t));
                }
            }
        } 
catch (Exception exe) {
        }
    }

    
/**
     * 把如00:00.00这样的字符串转化成
     * 毫秒数的时间,比如 
     * 01:10.34就是一分钟加上10秒再加上340毫秒
     * 也就是返回70340毫秒
     * 
@param time 字符串的时间
     * 
@return 此时间表示的毫秒
     
*/
    
private long parseTime(String time) {
        String[] ss 
= time.split("\\:|\\.");
        
//如果 是两位以后,就非法了
        if (ss.length < 2) {
            
return -1;
        } 
else if (ss.length == 2) {//如果正好两位,就算分秒
            try {
                
int min = Integer.parseInt(ss[0]);
                
int sec = Integer.parseInt(ss[1]);
                
if (min < 0 || sec < 0 || sec >= 60) {
                    
throw new RuntimeException("数字不合法!");
                }
                
return (min * 60 + sec) * 1000L;
            } 
catch (Exception exe) {
                
return -1;
            }
        } 
else if (ss.length == 3) {//如果正好三位,就算分秒,十毫秒
            try {
                
int min = Integer.parseInt(ss[0]);
                
int sec = Integer.parseInt(ss[1]);
                
int mm = Integer.parseInt(ss[2]);
                
if (min < 0 || sec < 0 || sec >= 60 || mm < 0 || mm > 99) {
                    
throw new RuntimeException("数字不合法!");
                }
                
return (min * 60 + sec) * 1000L + mm * 10;
            } 
catch (Exception exe) {
                
return -1;
            }
        } 
else {//否则也非法
            return -1;
        }
    }

    
/**
     * 设置其显示区域的高度
     * 
@param height 高度
     
*/
    
public void setHeight(int height) {
        
this.height = height;
    }

    
/**
     * 设置其显示区域的宽度
     * 
@param width 宽度
     
*/
    
public void setWidth(int width) {
        
this.width = width;
    }

    
/**
     * 设置时间
     * 
@param time 时间
     
*/
    
public void setTime(long time) {
        
if (!isMoving) {
            tempTime 
= this.time = time;
        }
    }

    
/**
     * 得到是否初始化完成了
     * 
@return 是否完成
     
*/
    
public boolean isInitDone() {
        
return initDone;
    }

    
private void drawKaraoke(Graphics2D gd, Sentence now, int x, int y, long t) {
        
int nowWidth = now.getContentWidth(gd);
        Color gradient 
= null;
        
//如果要渐入渐出才去求中间色,否则直接用高亮色画
        if (Config.getConfig().isLyricShadow()) {
            gradient 
= now.getBestInColor(Config.getConfig().getLyricHilight(), Config.getConfig().getLyricForeground(), t);
        } 
else {
            gradient 
= Config.getConfig().getLyricHilight();
        }
        
if (Config.getConfig().isKaraoke()) {
            
float f = (t - now.getFromTime()) * 1.0f / (now.getToTime() - now.getFromTime());
            
if (f > 0.98f) {
                f 
= 0.98f;
            }
            
if (x == 0) {
                x 
= 1;
            }
            
if (nowWidth == 0) {
                nowWidth 
= 1;
            }
            gd.setPaint(
new LinearGradientPaint(x, y, x + nowWidth, y, new float[]{f, f + 0.01f}, new Color[]{gradient, Config.getConfig().getLyricForeground()}));
        } 
else {
            gd.setPaint(gradient);
        }

        Util.drawString(gd, now.getContent(), x, y);
    }

    
/**
     * 自力更生,画出自己在水平方向的方法
     * 这个做是为了更方便地把歌词显示在
     * 任何想显示的地方
     * 
@param g 画笔
     
*/
    
public synchronized void drawH(Graphics g) {
        
if (!enabled) {
            Sentence sen 
= new Sentence(info.getFormattedName());
            
int x = (width - sen.getContentWidth(g)) / 2;
            
int y = (height - sen.getContentHeight(g) + Config.getConfig().getV_SPACE()) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, sen.getContent(), x, y);
            
return;
        }
        
//首先看是不是初始化完毕了
        if (!initDone) {
            Sentence temp 
= new Sentence("正在搜索歌词");
            
int x = (width - temp.getContentWidth(g)) / 2;
            
int y = (height - temp.getContentHeight(g)) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, temp.getContent(), x, y);
            
return;
        }
        
//如果只存在一句的话,那就不要浪费那么多计算的时候了
        
//直接画在中间就可以了
        if (list.size() == 1) {
            Sentence sen 
= list.get(0);
            
int x = (width - sen.getContentWidth(g)) / 2;
            
int y = (height - sen.getContentHeight(g) + Config.getConfig().getV_SPACE()) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, sen.getContent(), x, y);
        } 
else {
            
//取一个time的副本,以防止在一个方法里面产生两种time的情况
            long t = tempTime;
            Graphics2D gd 
= (Graphics2D) g;
            
int index = getNowSentenceIndex(t);
            
if (!isMoving) {
                currentIndex 
= index;
            }
            
if (index == -1) {
                Sentence sen 
= new Sentence(info.getFormattedName(), Integer.MIN_VALUE, Integer.MAX_VALUE);
                
int x = (width - sen.getContentWidth(g) - Config.getConfig().getH_SPACE()) / 2;
                
int y = (height - sen.getContentHeight(g) + Config.getConfig().getV_SPACE()) / 2;
                g.setColor(Config.getConfig().getLyricHilight());
                Util.drawString(g, sen.getContent(), x, y);
                
return;
            }
            Sentence now 
= list.get(index);
            
int nowWidth = now.getContentWidth(g) + Config.getConfig().getH_SPACE();
            
int x = (width) / 2 - now.getHIncrease(g, t);
            
int y = (height - now.getContentHeight(g)) / 2;
            
this.drawKaraoke(gd, now, x, y, t);
            gd.setPaint(Config.getConfig().getLyricForeground());
            
int tempX = x;
            
//画出中间那句之前的句子
            for (int i = index - 1; i >= 0; i--) {
                Sentence sen 
= list.get(i);
                
int wid = sen.getContentWidth(g) + Config.getConfig().getH_SPACE();
                tempX 
= tempX - wid;
                
if (tempX + wid < 0) {
                    
break;
                }
                
if (Config.getConfig().isLyricShadow()) {
                    
if (i == index - 1) {
                        gd.setPaint(sen.getBestOutColor(Config.getConfig().getLyricHilight(),
                                Config.getConfig().getLyricForeground(), time));
                    } 
else {
                        gd.setPaint(Config.getConfig().getLyricForeground());
                    }
                }
                Util.drawString(g, sen.getContent(), tempX, y);
            }
            gd.setPaint(Config.getConfig().getLyricForeground());
            tempX 
= x;
            
int tempWidth = nowWidth;
            
//画出中间那句之后的句子
            for (int i = index + 1; i < list.size(); i++) {
                Sentence sen 
= list.get(i);
                tempX 
= tempX + tempWidth;
                
if (tempX > width) {
                    
break;
                }
                Util.drawString(g, sen.getContent(), tempX, y);
                tempWidth 
= sen.getContentWidth(g) + Config.getConfig().getH_SPACE();
            }
        }
    }

    
/**
     * 得到这批歌词里面,最长的那一句的长度
     * 
@return 最长的长度
     
*/
    
public int getMaxWidth(Graphics g) {
        
int max = 0;
        
for (Sentence sen : list) {
            
int w = sen.getContentWidth(g);
            
if (w > max) {
                max 
= w;
            }
        }
        
return max;
    }

    
/**
     * 得到一句话的X座标,因为可能对齐方式有
     * 多种,针对每种对齐方式,X的座标不一
     * 定一样。
     * 
@param g 画笔
     * 
@param sen 要求的句子
     * 
@return 本句的X座标
     
*/
    
private int getSentenceX(Graphics g, Sentence sen) {
        
int x = 0;
        
int i = Config.getConfig().getLyricAlignMode();
        
switch (i) {
            
case Config.LYRIC_CENTER_ALIGN:
                x 
= (width - sen.getContentWidth(g)) / 2;
                
break;
            
case Config.LYRIC_LEFT_ALIGN:
                x 
= 0;
                
break;
            
case Config.LYRIC_RIGHT_ALIGN:
                x 
= width - sen.getContentWidth(g);
                
break;
            
default://默认情况还是中间对齐
                x = (width - sen.getContentWidth(g)) / 2;
                
break;
        }
        
return x;
    }

    
/**
     * 画出自己在垂直方向上的过程
     * 
@param g 画笔
     
*/
    
public synchronized void drawV(Graphics g) {
        
if (!enabled) {
            Sentence sen 
= new Sentence(info.getFormattedName());
            
int x = (width - sen.getContentWidth(g)) / 2;
            
int y = (height - sen.getContentHeight(g) + Config.getConfig().getV_SPACE()) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, sen.getContent(), x, y);
            
return;
        }
        
//首先看是不是初始化完毕了
        if (!initDone) {
            Sentence temp 
= new Sentence("正在搜索歌词");
            
int x = getSentenceX(g, temp);
            
int y = (height - temp.getContentHeight(g)) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, temp.getContent(), x, y);
            
return;
        }
        
//如果只存在一句的话,那就不要浪费那么多计算的时候了
        
//直接画在中间就可以了
        if (list.size() == 1) {
            Sentence sen 
= list.get(0);
            
int x = getSentenceX(g, sen);
            
int y = (height - sen.getContentHeight(g)) / 2;
            g.setColor(Config.getConfig().getLyricHilight());
            Util.drawString(g, sen.getContent(), x, y);
        } 
else {
            
long t = tempTime;
            Graphics2D gd 
= (Graphics2D) g;
            
int index = getNowSentenceIndex(t);
            
if (!isMoving) {
                currentIndex 
= index;
            }
            
if (index == -1) {
                Sentence sen 
= new Sentence(info.getFormattedName(), Integer.MIN_VALUE, Integer.MAX_VALUE);
                
int x = getSentenceX(g, sen);
                
int y = (height - sen.getContentHeight(g)) / 2;
                gd.setPaint(Config.getConfig().getLyricHilight());
                Util.drawString(g, sen.getContent(), x, y);
                
return;
            }
            Sentence now 
= list.get(index);
            
//先求出中间的最基准的纵座标
            int y = (height + now.getContentHeight(g)) / 2 - now.getVIncrease(g, t);
            
int x = getSentenceX(g, now);
            
this.drawKaraoke(gd, now, x, y, t);
            gd.setColor(Config.getConfig().getLyricForeground());
            
//然后再画上面的部份以及下面的部份
            
//这样就可以保证正在唱的歌词永远在正中间显示
            int tempY = y;
            
//画出本句之前的句子
            for (int i = index - 1; i >= 0; i--) {
                Sentence sen 
= list.get(i);
                
int x1 = getSentenceX(g, sen);
                tempY 
= tempY - sen.getContentHeight(g) - Config.getConfig().getV_SPACE();
                
if (tempY + sen.getContentHeight(g) < 0) {
                    
break;
                }
                
if (Config.getConfig().isLyricShadow()) {
                    
if (i == index - 1) {
                        gd.setColor(sen.getBestOutColor(Config.getConfig().getLyricHilight(),
                                Config.getConfig().getLyricForeground(), time));
                    } 
else {
                        gd.setColor(Config.getConfig().getLyricForeground());
                    }
                }
                Util.drawString(g, sen.getContent(), x1, tempY);
            }
            gd.setColor(Config.getConfig().getLyricForeground());
            tempY 
= y;
            
//画出本句之后的句子 
            for (int i = index + 1; i < list.size(); i++) {
                Sentence sen 
= list.get(i);
                
int x1 = getSentenceX(g, sen);
                tempY 
= tempY + sen.getContentHeight(g) + Config.getConfig().getV_SPACE();
                
if (tempY > height) {
                    
break;
                }
                Util.drawString(g, sen.getContent(), x1, tempY);
            }
        }
    }

    
/**
     * 得到当前正在播放的那一句的下标
     * 不可能找不到,因为最开头要加一句
     * 自己的句子 ,所以加了以后就不可能找不到了
     * 
@return 下标
     
*/
    
private int getNowSentenceIndex(long t) {
        
for (int i = 0; i < list.size(); i++) {
            
if (list.get(i).isInTime(t)) {
                
return i;
            }
        }
//        throw new RuntimeException("竟然出现了找不到的情况!");
        return -1;
    }

    
/**
     * 水平移动多少个象素,这个方法是给面板调用的
     * 移动了这些象素以后,要马上算出这个象素所
     * 对应的时间是多少,要注意时间超出的情况
     * 
@param length
     * 
@param g 画笔,因为对于每一个画笔长度不一样的
     
*/
    
public void moveH(int length, Graphics g) {
        
if (list.size() == 1 || !enabled) {
            
return;
        }
        
//如果长度是大于0的,则说明是正向移动,快进
        if (length > 0) {
            Sentence now 
= list.get(currentIndex);
            
int nowWidth = now.getContentWidth(g);
            
float f = (time - now.getFromTime()) * 1.0f / (now.getToTime() - now.getFromTime());
            
//先算出当前的这一句还剩多少长度了
            int rest = (int) ((1 - f) * nowWidth);
            
long timeAdd = 0;//要加多少时间
            
//如果剩下的长度足够了,那是最好,马上就可以返回了
            if (rest > length) {
                timeAdd 
= now.getTimeH(length, g);
            } 
else {
                timeAdd 
= now.getTimeH(rest, g);
                
for (int i = currentIndex; i < list.size(); i++) {
                    Sentence sen 
= list.get(i);
                    
int len = sen.getContentWidth(g);
                    
//如果加上下一句的长度还不够,就把时间再加,继续下一句
                    if (len + rest < length) {
                        timeAdd 
+= sen.getDuring();
                        rest 
+= len;
                    } 
else {
                        timeAdd 
+= sen.getTimeH(length - rest, g);
                        
break;
                    }
                }
            }
            tempTime 
= time + timeAdd;
            checkTempTime();
        } 
else {//否则就是反向移动,要快退了
            length = 0 - length;//取它的正数
            Sentence now = list.get(currentIndex);
            
int nowWidth = now.getContentWidth(g);
            
float f = (time - now.getFromTime()) * 1.0f / (now.getToTime() - now.getFromTime());
            
//先算出当前的这一句已经用了多少长度了
            int rest = (int) (f * nowWidth);
            
long timeAdd = 0;//要加多少时间
            
//如果剩下的长度足够了,那是最好,马上就可以返回了
            if (rest > length) {
                timeAdd 
= now.getTimeH(length, g);
            } 
else {
                timeAdd 
= now.getTimeH(rest, g);
                
for (int i = currentIndex; i > 0; i--) {
                    Sentence sen 
= list.get(i);
                    
int len = sen.getContentWidth(g);
                    
//如果加上下一句的长度还不够,就把时间再加,继续下一句
                    if (len + rest < length) {
                        timeAdd 
+= sen.getDuring();
                        rest 
+= len;
                    } 
else {
                        timeAdd 
+= sen.getTimeH(length - rest, g);
                        
break;
                    }
                }
            }
            tempTime 
= time - timeAdd;
            checkTempTime();
        }
    }

    
/**
     * 竖直移动多少个象素,这个方法是给面板调用的
     * 移动了这些象素以后,要马上算出这个象素所
     * 对应的时间是多少,要注意时间超出的情况
     * 
@param length
     * 
@param g 画笔,因为对于每一个画笔长度不一样的
     
*/
    
public void moveV(int length, Graphics g) {
        
if (list.size() == 1 || !enabled) {
            
return;
        }
        
//如果长度是大于0的,则说明是正向移动,快进
        if (length > 0) {
            Sentence now 
= list.get(currentIndex);
            
int nowHeight = now.getContentHeight(g);
            
float f = (time - now.getFromTime()) * 1.0f / (now.getToTime() - now.getFromTime());
            
//先算出当前的这一句还剩多少长度了
            int rest = (int) ((1 - f) * nowHeight);
            
long timeAdd = 0;//要加多少时间
            
//如果剩下的长度足够了,那是最好,马上就可以返回了
            if (rest > length) {
                timeAdd 
= now.getTimeV(length, g);
            } 
else {
                timeAdd 
= now.getTimeV(rest, g);
                
for (int i = currentIndex; i < list.size(); i++) {
                    Sentence sen 
= list.get(i);
                    
int len = sen.getContentHeight(g);
                    
//如果加上下一句的长度还不够,就把时间再加,继续下一句
                    if (len + rest < length) {
                        timeAdd 
+= sen.getDuring();
                        rest 
+= len;
                    } 
else {
                        timeAdd 
+= sen.getTimeV(length - rest, g);
                        
break;
                    }
                }
            }
            tempTime 
= time + timeAdd;
            checkTempTime();
        } 
else {//否则就是反向移动,要快退了
            length = 0 - length;//取它的正数
            Sentence now = list.get(currentIndex);
            
int nowHeight = now.getContentHeight(g);
            
float f = (time - now.getFromTime()) * 1.0f / (now.getToTime() - now.getFromTime());
            
//先算出当前的这一句已经用了多少长度了
            int rest = (int) (f * nowHeight);
            
long timeAdd = 0;//要加多少时间
            
//如果剩下的长度足够了,那是最好,马上就可以返回了
            if (rest > length) {
                timeAdd 
= now.getTimeV(length, g);
            } 
else {
                timeAdd 
= now.getTimeV(rest, g);
                
for (int i = currentIndex; i > 0; i--) {
                    Sentence sen 
= list.get(i);
                    
int len = sen.getContentHeight(g);
                    
//如果加上下一句的长度还不够,就把时间再加,继续下一句
                    if (len + rest < length) {
                        timeAdd 
+= sen.getDuring();
                        rest 
+= len;
                    } 
else {
                        timeAdd 
+= sen.getTimeV(length - rest, g);
                        
break;
                    }
                }
            }
            tempTime 
= time - timeAdd;
            checkTempTime();
        }
    }

    
/**
     * 是否能拖动,只有有歌词才可以被拖动,否则没有意义了
     * 
@return 能否拖动
     
*/
    
public boolean canMove() {
        
return list.size() > 1 && enabled;
    }

    
/**
     * 得到当前的时间,一般是由显示面板调用的
     
*/
    
public long getTime() {
        
return tempTime;
    }

    
/**
     * 在对tempTime做了改变之后,检查一下它的
     * 值,看是不是在有效的范围之内
     
*/
    
private void checkTempTime() {
        
if (tempTime < 0) {
            tempTime 
= 0;
        } 
else if (tempTime > during) {
            tempTime 
= during;
        }
    }

    
/**
     * 告诉歌词,要开始移动了,
     * 在此期间,所有对歌词的直接的时间设置都不理会
     
*/
    
public void startMove() {
        isMoving 
= true;
    }

    
/**
     * 告诉歌词拖动完了,这个时候的时间改
     * 变要理会,并做更改
     
*/
    
public void stopMove() {
        isMoving 
= false;
    }

    
public static void main(String[] args) {

    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package com.hadeslee.yoyoplayer.lyric;

import com.hadeslee.yoyoplayer.util.Config;
import com.hadeslee.yoyoplayer.util.Util;
import java.awt.Color;
import java.awt.Graphics;
import java.io.Serializable;

/**
 * 一个用来表示每一句歌词的类
 * 它封装了歌词的内容以及这句歌词的起始时间
 * 和结束时间,还有一些实用的方法
 * 
@author hadeslee
 
*/
public class Sentence implements Serializable {

    
private static final long serialVersionUID = 20071125L;
    
private long fromTime;//这句的起始时间,时间是以毫秒为单位
    private long toTime;//这一句的结束时间
    private String content;//这一句的内容
    private final static long DISAPPEAR_TIME = 1000L;//歌词从显示完到消失的时间
    public Sentence(String content, long fromTime, long toTime) {
        
this.content = content;
        
this.fromTime = fromTime;
        
this.toTime = toTime;
    }

    
public Sentence(String content, long fromTime) {
        
this(content, fromTime, 0);
    }

    
public Sentence(String content) {
        
this(content, 00);
    }

    
public long getFromTime() {
        
return fromTime;
    }

    
public void setFromTime(long fromTime) {
        
this.fromTime = fromTime;
    }

    
public long getToTime() {
        
return toTime;
    }

    
public void setToTime(long toTime) {
        
this.toTime = toTime;
    }

    
/**
     * 检查某个时间是否包含在某句中间
     * 
@param time 时间
     * 
@return 是否包含了
     
*/
    
public boolean isInTime(long time) {
        
return time >= fromTime && time <= toTime;
    }

    
/**
     * 得到这一句的内容
     * 
@return 内容
     
*/
    
public String getContent() {
        
return content;
    }

    
/**
     * 得到V方向的增量
     * 
@param time 时间
     * 
@return 增量
     
*/
    
public int getVIncrease(Graphics g, long time) {
        
int height = getContentHeight(g);
        
return (int) ((height + Config.getConfig().getV_SPACE()) * ((time - fromTime) * 1.0 / (toTime - fromTime)));
    }

    
/**
     * 得到H向方的增量
     * 
@param time 时间
     * 
@return 增时
     
*/
    
public int getHIncrease(Graphics g, long time) {
        
int width = getContentWidth(g);
        
return (int) ((width + Config.getConfig().getH_SPACE()) * ((time - fromTime) * 1.0 / (toTime - fromTime)));
    }

    
/**
     * 得到内容的宽度
     * 
@param g 画笔
     * 
@return 宽度
     
*/
    
public int getContentWidth(Graphics g) {
        
return (int) g.getFontMetrics().getStringBounds(content, g).getWidth();
    }

    
/**
     * 得到这个句子的时间长度,毫秒为单位
     * 
@return 长度
     
*/
    
public long getDuring() {
        
return toTime - fromTime;
    }

    
/**
     * 移动这些距离来说,对于这个句子
     * 花了多少的时间
     * 
@param length 要移动的距离
     * 
@param g 画笔
     * 
@return 时间长度
     
*/
    
public long getTimeH(int length, Graphics g) {
        
return getDuring() * length / getContentWidth(g);
    }

    
/**
     * 对于竖直方向的移动这些象素所代表的时间
     * 
@param length 距离的长度
     * 
@param g 画笔
     * 
@return 时间长度
     
*/
    
public long getTimeV(int length, Graphics g) {
        
return getDuring() * length / getContentHeight(g);
    }

    
/**
     * 得到内容的高度
     * 
@param g 画笔
     * 
@return 高度
     
*/
    
public int getContentHeight(Graphics g) {
        
return (int) g.getFontMetrics().getStringBounds(content, g).getHeight() + Config.getConfig().getV_SPACE();
    }

    
/**
     * 根据当前指定的时候,得到这个时候应该
     * 取渐变色的哪个阶段了,目前的算法是从
     * 快到结束的五分之一处开始渐变,这样平缓一些
     * 
@param c1 高亮色
     * 
@param c2 普通色
     * 
@param time 时间
     * 
@return 新的颜色
     
*/
    
public Color getBestInColor(Color c1, Color c2, long time) {
        
float f = (time - fromTime) * 1.0f / getDuring();
        
if (f > 0.1f) {//如果已经过了十分之一的地方,就直接返高亮色
            return c1;
        } 
else {
            
long dur = getDuring();
            f 
= (time - fromTime) * 1.0f / (dur * 0.1f);
            
if (f > 1 || f < 0) {
                
return c1;
            }
            
return Util.getGradientColor(c2, c1, f);
        }
    }

    
/**
     * 得到最佳的渐出颜色
     * 
@param c1
     * 
@param c2
     * 
@param time
     * 
@return
     
*/
    
public Color getBestOutColor(Color c1, Color c2, long time) {
        
if (isInTime(time)) {
            
return c1;
        }
        
float f = (time - toTime) * 1.0f / DISAPPEAR_TIME;
        
if (f > 1f || f <= 0) {//如果时间已经超过了最大的时间了,则直接返回原来的颜色
            return c2;
        } 
else {
            
return Util.getGradientColor(c1, c2, f);
        }
    }

    
public String toString() {
        
return "{" + fromTime + "(" + content + ")" + toTime + "}";
    }
    }


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package com.hadeslee.yoyoplayer.lyric;

import com.hadeslee.yoyoplayer.util.Config;
import com.hadeslee.yoyoplayer.util.Playerable;
import com.hadeslee.yoyoplayer.util.Util;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

/**
 *
 * 
@author hadeslee
 
*/
public class LyricPanel extends JPanel implements Runnable, DropTargetListener,
        MouseListener, MouseWheelListener, MouseMotionListener {

    
private static final long serialVersionUID = 20071214L;
    
private static Logger log = Logger.getLogger(LyricPanel.class.getName());
    
private DropTarget dt;//一个拖放的目标
    private Playerable player;//播放器
    private Lyric ly;//表示此歌词面板对应的歌词对象
    public static final int V = 0;//表示纵向显示
    public static final int H = 1;//表示横向显示
    private int state = H;//表示现在是横向还是纵向的
    private volatile boolean isPress;//是已经按下,按下就就不滚动歌词了
    private volatile boolean isDrag;//是否已经动过了
    private int start;//开始的时候座标,在释放的时候,好计算拖了多少
    private int end;//现在的座标
    private volatile boolean isResized;//是否已经重设大小了
    private volatile boolean pause = true;//一个循环的标量
    private final Object lock = new Object();
    
private volatile boolean isOver;//是否手在上面
    private Rectangle area = new Rectangle();
    
private final String logo = "作者:千里冰封";
    
private boolean isShowLogo = true;
    
private Config config;//一个全局配置对象
//    private Component parent;//它是被加到谁的身上去了
    public LyricPanel(Playerable pl) {
        
this();
        
this.player = pl;
        
this.setDoubleBuffered(true);
    }

    
public LyricPanel() {
        config 
= Config.getConfig();
        dt 
= new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this);
        state 
= config.getLpState();
        
this.addMouseListener(this);
        
this.addMouseMotionListener(this);
        
this.addMouseWheelListener(this);
        Thread th 
= new Thread(this);
        th.setDaemon(
true);
        th.start();
    }

    
public void setConfig(Config config) {
        
this.config = config;
    }

    
public void setShowLogo(boolean b) {
        isShowLogo 
= b;
    }

    
/**
     * 设置播放列表
     * 
@param pl 播放列表
     
*/
    
public void setPlayList(Playerable pl) {
        
this.player = pl;
    }

    
/**
     * 设置一个新的歌词对象,此方法可能会被
     * PlayList调用
     * 
@param ly 歌词
     
*/
    
public void setLyric(Lyric ly) {
        
this.ly = ly;
        isResized 
= false;
    }

    
public void pause() {
        log.log(Level.INFO, 
"歌词暂停显示了");
        pause 
= true;
    }

    
public void start() {
        log.log(Level.INFO, 
"歌词开始显示了");
        pause 
= false;
        
synchronized (lock) {
            lock.notifyAll();
        }
    }

    
protected void paintComponent(Graphics g) {
        Graphics2D gd 
= (Graphics2D) g;
        
if (config.isTransparency()) {

        } 
else {
            
super.paintComponent(g);
            gd.setColor(config.getLyricBackground());
            gd.fillRect(
00, getWidth(), getHeight());
        }
        
if (config.isAntiAliasing()) {
            gd.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
//            gd.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        }
        g.setFont(config.getLyricFont());
        state 
= config.getLpState();
        
if (ly != null) {
            
//只有要重设大小,并且没有重设大小的时候,才去设,否则就不用理它了
            
//并且还要不是水平显示,因为水平显示的话,宽度就没有意义了,想多宽就可以多宽
            if (config.isAutoResize() && !isResized && ly.isInitDone() && state == V) {
                
int maxWidth = ly.getMaxWidth(g);
                
int inset = player.getLyricUI().getInsets().left + player.getLyricUI().getInsets().right;
                JDialog win 
= config.getLrcWindow();
                
if (win != null) {
                    win.setSize(maxWidth 
+ inset, win.getHeight());
                    isResized 
= true;
                }

            }
            
if (isPress && isDrag) {
                
if (state == H) {
                    ly.moveH(start 
- end, g);
                } 
else {
                    ly.moveV(start 
- end, g);
                }
            }
            
if (state == H) {
                ly.drawH(g);
            } 
else {
                ly.drawV(g);
            }
            
if (isPress && isDrag) {
                
if (state == H) {
                    drawTimeH((
int) (ly.getTime() / 1000), g);
                } 
else {
                    drawTimeV((
int) (ly.getTime() / 1000), g);
                }

            }
        } 
else {
            g.setColor(config.getLyricHilight());
            
int width = Util.getStringWidth(Config.NAME, g);
            
int height = Util.getStringHeight(Config.NAME, g);
            Util.drawString(g, Config.NAME, (getWidth() 
- width) / 2, (getHeight() - height) / 2);
        }
        
if (isShowLogo) {
            drawLogo(g);
        }
    }

    
/**
     * 画出自己的LOGO
     * 
@param g 画笔
     
*/
    
private void drawLogo(Graphics g) {
        g.setFont(
new Font("Dialog", Font.BOLD, 14));
        
int width = Util.getStringWidth(logo, g);
        
int height = Util.getStringHeight(logo, g);
        area.x 
= 5;
        area.y 
= 5;
        area.width 
= width;
        area.height 
= height;
        
if (isOver) {
            g.setColor(Color.RED);
        } 
else {
            Color bg 
= config.getLyricBackground();
            
int rgb = bg.getRGB();
            
int xor = ~rgb;
            rgb 
= xor & 0x00ffffff;
            Color c 
= new Color(rgb);
            g.setColor(c);
        }
        Util.drawString(g, 
"作者:千里冰封"55);
    }

    
/**
     * 得到播放器对象,此方法一般是给
     * 在线搜索歌词框用的
     * 
@return 播放器
     
*/
    
public Playerable getPlayer() {
        
return this.player;
    }

    
/**
     * 画出正在拖动的时候的时间,以便更好的掌握进度
     * 这是画出垂直方向的拖动时间
     * 
@param sec 当前的秒数
     * 
@param g 画笔
     
*/
    
private void drawTimeV(int sec, Graphics g) {
        String s 
= Util.secondToString(sec);
        
int width = getWidth();
        
int height = getHeight();
        
int centerY = height / 2;

        g.drawLine(
3, centerY - 53, centerY + 5);
        g.drawLine(width 
- 3, centerY - 5, width - 3, centerY + 5);
        g.drawLine(
3, centerY, width - 3, centerY);
        g.setFont(
new Font("宋体", Font.PLAIN, 14));
        g.setColor(Util.getColor(config.getLyricForeground(), config.getLyricHilight()));
        Util.drawString(g, s, width 
- Util.getStringWidth(s, g), (height / 2 - Util.getStringHeight(s, g)));
    }

    
/**
     * 画出正在拖动的时候的时间,以便更好的掌握进度
     * 这是画出水平方向的拖动时间
     * 
@param sec 当前的秒数
     * 
@param g 画笔
     
*/
    
private void drawTimeH(int sec, Graphics g) {
        String s 
= Util.secondToString(sec);
        
int centerX = getWidth() / 2;
        
int height = getHeight();

        g.drawLine(centerX 
- 53, centerX + 53);
        g.drawLine(centerX 
- 5, height - 3, centerX + 5, height - 3);
        g.drawLine(centerX, 
3, centerX, height - 3);
        g.setFont(
new Font("宋体", Font.PLAIN, 14));
        g.setColor(Util.getColor(config.getLyricForeground(), config.getLyricHilight()));
        Util.drawString(g, s, centerX, (height 
- Util.getStringHeight(s, g)));
    }

    
public void run() {
        
while (true) {
            
try {
                Thread.sleep(config.getRefreshInterval());
                
if (pause) {
                    
synchronized (lock) {
                        lock.wait();
                    }
                } 
else {
                    
if (ly != null) {
                        ly.setHeight(
this.getHeight());
                        ly.setWidth(
this.getWidth());
                        ly.setTime(player.getTime());
                        repaint();
                    }
                }
            } 
catch (Exception exe) {
                exe.printStackTrace();
            }
        }
    }

    
public void dragEnter(DropTargetDragEvent dtde) {
        dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
    }

    
public void dragOver(DropTargetDragEvent dtde) {
    }

    
public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    
public void dragExit(DropTargetEvent dte) {
    }

    
public void drop(DropTargetDropEvent e) {
        
try {
            
//得到操作系统的名字,如果是windows,则接受的是DataFlavor.javaFileListFlavor
            
//如果是linux则接受的是DataFlavor.stringFlavor
            String os = System.getProperty("os.name");
            
if (os.startsWith("Windows")) {
                
if (e.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                    Transferable tr 
= e.getTransferable();
                    e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                    @SuppressWarnings(
"unchecked")
                    java.util.List
<File> s = (java.util.List<File>) tr.getTransferData(
                            DataFlavor.javaFileListFlavor);
                    
if (s.size() == 1) {
                        File f 
= s.get(0);
                        
if (f.isFile() && player.getCurrentItem() != null) {
                            ly 
= new Lyric(f, player.getCurrentItem());
                            ly.setWidth(
this.getWidth());
                            ly.setHeight(
this.getHeight());
                            player.setLyric(ly);
                        }
                    }
                    e.dropComplete(
true);
                }
            } 
else if (os.startsWith("Linux")) {
                
if (e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                    e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                    Transferable tr 
= e.getTransferable();
                    String[] ss 
= tr.getTransferData(DataFlavor.stringFlavor).toString().split("\r\n");
                    
if (ss.length == 1) {
                        File f 
= new File(new URI(ss[0]));
                        
if (f.isFile() && player.getCurrentItem() != null) {
                            ly 
= new Lyric(f, player.getCurrentItem());
                            ly.setWidth(
this.getWidth());
                            ly.setHeight(
this.getHeight());
                            player.setLyric(ly);
                        }
                    }
                    e.dropComplete(
true);
                }
            } 
else {
                e.rejectDrop();
            }
        } 
catch ( Exception io) {
            io.printStackTrace();
            e.rejectDrop();
        }
    }

    
public void setState(int state) {
        
if (state == H || state == V) {
            
this.state = state;
        }
    }

    
public void setResized(boolean b) {
        isResized 
= b;
    }

    
public void mouseClicked(MouseEvent e) {
//        //双击的时候,改变显示风格
//        if (e.getClickCount() == 2) {
//            if (state == H) {
//                state = V;
//            } else {
//                state = H;
//            }
//        }
    }

    
public void mousePressed(MouseEvent e) {
        
if (ly == null) {
            
return;
        }
        
if (e.getButton() == MouseEvent.BUTTON1) {
            
if (area != null && area.contains(e.getPoint())) {
                
try {
                    Desktop.getDesktop().browse(
new URI("http://www.blogjava.net/hadeslee"));
                } 
catch (URISyntaxException ex) {
                    Logger.getLogger(LyricPanel.
class.getName()).log(Level.SEVERE, null, ex);
                } 
catch (IOException ex) {
                    Logger.getLogger(LyricPanel.
class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            
if (ly != null && ly.canMove()) {
                isPress 
= true;
                isDrag 
= false;
                
if (state == V) {
                    start 
= e.getY();
                } 
else {
                    start 
= e.getX();
                }
                ly.startMove();
            }
        }
    }

    
public void mouseReleased(MouseEvent e) {
        
if (ly == null) {
            
return;
        }
        
//如果是鼠标左键
        if (e.getButton() == MouseEvent.BUTTON1) {
            
if (ly.canMove() && isDrag) {
                
if (state == H) {
                    end 
= e.getX();
                } 
else {
                    end 
= e.getY();
                }
                
long time = ly.getTime();
                player.setTime(time);
                start 
= end = 0;
            }
            ly.stopMove();
            isPress 
= false;
            isDrag 
= false;
        
//如果是鼠标右键
        } else if (e.getButton() == MouseEvent.BUTTON3) {
            
if (player.getCurrentItem() == null) {
                
return;
            }
            JPopupMenu pop 
= new JPopupMenu();
            Util.generateLyricMenu(pop, 
this);
            pop.show(
this, e.getX(), e.getY());
        }
    }

    
/**
     * 隐藏自己
     
*/
    
public void hideMe() {
        player.setShowLyric(
false);
    }

    
public Lyric getLyric() {
        
return ly;
    }

    
public void mouseEntered(MouseEvent e) {
        
if (ly != null && ly.canMove()) {
            
this.setCursor(new Cursor(Cursor.HAND_CURSOR));
        } 
else {
            
this.setCursor(Cursor.getDefaultCursor());
        }
    }

    
public void mouseExited(MouseEvent e) {
        
this.setCursor(Cursor.getDefaultCursor());
        isOver 
= false;
    }

    
public void mouseWheelMoved(MouseWheelEvent e) {
        
if (ly == null) {
            
return;
        }
        
//只有当配置允许鼠标滚动调整时间才可以
        if (config.isMouseScrollAjustTime()) {
            
int adjust = e.getUnitsToScroll() * 100;//每转动一下,移动300毫秒
            ly.adjustTime(adjust);
        }
    }

    
public void mouseDragged(MouseEvent e) {
        
if (ly == null) {
            
return;
        }
        
if (ly.canMove() && isPress) {
            isDrag 
= true;
            
if (state == H) {
                end 
= e.getX();
            } 
else {
                end 
= e.getY();
            }
        }
    }

    
public void mouseMoved(MouseEvent e) {
        
if (area != null && area.contains(e.getPoint())) {
            isOver 
= true;
            
this.setCursor(new Cursor(Cursor.HAND_CURSOR));
        } 
else {
            isOver 
= false;
            mouseEntered(e);
        }
    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package com.hadeslee.yoyoplayer.lyric;

import com.hadeslee.yoyoplayer.util.Config;
import com.hadeslee.yoyoplayer.util.MultiImageBorder;
import com.hadeslee.yoyoplayer.util.Playerable;
import com.hadeslee.yoyoplayer.util.Util;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import javax.swing.JPanel;

/**
 *
 * 
@author hadeslee
 
*/
public class LyricUI extends JPanel {

    
private static final long serialVersionUID = 20071214L;
    
private Config config;
    
private LyricPanel lp;//一个实际显示歌词的面板
    private Playerable player;
    
private MultiImageBorder border;//即是边界,又是监听器
    public LyricUI() {
        
super(new BorderLayout());
        
this.setPreferredSize(new Dimension(285465));
        
this.setMinimumSize(new Dimension(28550));
    }

    
public void setPlayer(Playerable player) {
        
this.player = player;
    }
    
public void setParent(Component parent){
        border.setParent(parent);
    }
    
public void setBorderEnabled(boolean b){
        
if(b){
            
this.setBorder(border);
        }
else{
            
this.setBorder(null);
        }
    }
    
public void loadUI(Component parent, Config config) {
        
this.config = config;
        border 
= new MultiImageBorder(parent, config);
        border.setCorner1(Util.getImage(
"lyric/corner1.png"));
        border.setCorner2(Util.getImage(
"playlist/corner2.png"));
        border.setCorner3(Util.getImage(
"playlist/corner3.png"));
        border.setCorner4(Util.getImage(
"playlist/corner4.png"));
        border.setTop(Util.getImage(
"playlist/top.png"));
        border.setBottom(Util.getImage(
"playlist/bottom.png"));
        border.setLeft(Util.getImage(
"playlist/left.png"));
        border.setRight(Util.getImage(
"playlist/right.png"));
        
this.setBorder(border);
        
this.addMouseListener(border);
        
this.addMouseMotionListener(border);
        lp 
= new LyricPanel(player);
        lp.setConfig(config);
        
this.add(lp, BorderLayout.CENTER);
    }

    
public void setShowLogo(boolean b) {
        lp.setShowLogo(b);
    }

    
/**
     * 设置播放列表
     * 
@param pl 播放列表
     
*/
    
public void setPlayList(Playerable pl) {
        lp.setPlayList(player);
    }

    
public LyricPanel getLyricPanel() {
        
return lp;
    }

    
/**
     * 设置一个新的歌词对象,此方法可能会被
     * PlayList调用
     * 
@param ly 歌词
     
*/
    
public void setLyric(Lyric ly) {
        lp.setLyric(ly);
    }

    
public void pause() {
        lp.pause();
    }

    
public void start() {
        lp.start();
    }
}



几乎每个方法都有注释,如果还有什么不太清楚的地方,可以留言,大家共同探讨这方面的话题,以让歌词显示的效率更高一些.




尽管千里冰封
依然拥有晴空

你我共同品味JAVA的浓香.
posted on 2008-01-10 21:52 千里冰封 阅读(7940) 评论(15)  编辑  收藏 所属分类: JAVASE

FeedBack:
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-01-10 22:39 | faen
千里冰封 你在哪儿上班啊?  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-01-11 10:15 | xan
YOYOPlayer 这个名字因为它的由来而变得浪漫,羡慕  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-01-11 14:47 | 千里冰封
@xan
谢谢你的夸奖:)  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-04-05 05:31 | JAVA_我要学!
有些包是你自己写的吧?能不能把那些包的代码,放到网上来?...没有那些包的源码,我看得头都晕了...T_T  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-04-05 05:35 | JAVA_我要学!
还有,能不能把处理歌词的原理和代码详细地解释一下...谢谢!  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-04-05 12:41 | 千里冰封
@JAVA_我要学!
详细的代码在http://sourceforge.net/projects/yoyoplayer  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-04-05 14:12 | JAVA_我要学!
@千里冰封
谢谢!  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2008-05-30 16:32 | hefainsky@qq.com
非常感谢楼主,最近也在学习楼主的代码,受益匪浅。

想请教大家:我想通过java调用window的api 获取window的基本信息,该用什么方法呢?网上查过,好像有jni可以尝试,不知道楼主有什么经验可以指导下不呢?

另外:YOYOPlayer中有定时关机的功能,这个算是调用window api吗,如何做到的呢?

希望楼主能邮件或者msn方式给予建议,谢谢!
msn:hefanxinsky@hotmail.com  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2009-11-19 02:50 | 忽悠路人甲
你强,你很强,牛逼得让我佩服得五体投地..
这页的代码是如此的精华!
如果可以的话请教一教我,我也想做一个属于自己的MP3播放器.但是从看了你的代码后,完全呆住了.让我觉得好迷茫,变得无从入手了.
如果你愿意的话请加我QQ:492343243  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示[未登录]
2010-10-14 15:28 | kevin
据说将要支持歌词编辑的功能了,希望这个功能可以比千千静听的强大。需求是这样的:歌词已经有了但是每句前面没有打上时间标记,希望可以边听歌边为歌词打时间戳,比如按一个快捷键就会把当前歌曲的时间插入到当前行的最前面。另外,yoyo会不会发android版啊  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2011-08-24 18:00 | JohnCookie
非常感谢 这两天研究LZ的这些代码 受益颇深  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2011-11-18 20:48 | 流星雨
做安卓手机播放器走到歌词这一关卡住了,嘿嘿,来您这看到代码这么的详细,回去好好研究一下,谢谢分享!  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2012-02-04 12:05 | 安徽李干
我也在写同步播放功能的实现。。。在将当前媒体时间与lrc歌词时间匹配的时候,老是卡死,不过在我写的一个小程序中是可以的,,我若将匹配歌词的任务在一个新线程中运行,,是不是就不影响主线程了??就像这样:
new Thread() {

public void run() {
........
........
}
}.start();
}

求解答。。  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2012-02-04 12:16 | 安徽李干
@流星雨
我也是啊 你解决了没??  回复  更多评论
  
# re: YOYOPlayer开发手记(四)歌词同步显示
2012-05-28 14:54 | 云云云
你强,你很强,牛逼得让我佩服得五体投地..   回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: