YOYOPlayer开发手记(四)歌词同步显示

YOYOPlayer开发手记(四)歌词同步显示

在最开始写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(
0 new  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,  0 0 );
    }

    
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(
0 0 , 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, 
" 作者:千里冰封 " 5 5 );
    }

    
/**
     * 得到播放器对象,此方法一般是给
     * 在线搜索歌词框用的
     * 
@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  -   5 3 , 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 
-   5 3 , centerX  +   5 3 );
        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( 285 465 ));
        
this .setMinimumSize( new  Dimension( 285 50 ));
    }

    
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的浓香.

你可能感兴趣的:(YOYOPlayer开发手记(四)歌词同步显示)