Android 超简单音乐播放器(十)歌词的实现

关于歌词

有下面这些:

  • 歌词的获取

  • 歌词的解析

  • 自定义View


歌词的获取

歌词的获取分为两种,一种是从本地一种是通过网络上提供的API获取。我选择的是歌词迷的API http://api.geci.me/en/latest/ 说实话,这个API并不是很好用,因为很多歌它都无法提供歌词。但是我懒得去找其他的啦,所以就用它好啦。

  • 首先,我们要(-。-;)不知道这里怎么说,就说通过API找到我们需要的数据吧
    和之前获取热门和搜索网络歌曲一样,因为这个API是根据歌名查询的,所以我们需要传入一个String name。但是这个地方需要注意,有的歌曲名可能会有空格,这样会影响我们获取歌词,所以,需要把空格都去掉。总的来说这里问题并不难..但是我弄了很久,就是因为这个空格问题,导致我每首歌曲查询都是没有歌词0 0让我产生了一种错觉,是我使用了(line = in.readLine() )!= null 的问题(这里摸一把泪( ▼-▼ ))
 public static void requstLrcData(final String name,okhttp3.Callback callback){
        OkHttpClient client = new OkHttpClient();
        Log.i(TAG, "requstLrcData: "+name);
        String name1 = name.trim();//去掉空格
        Log.i(TAG, "requstLrcData:"+name1);
        Request request = new Request.Builder()
                .url("http://gecimi.com/api/lyric/"+name1)
                .build();
        client.newCall(request).enqueue(callback);
    }
  • 然后我们就要解析我们得到的数据
    通过官方文档我们可以看到,会提供给我们歌词文件以及相应的数量。一步一步解析出自己需要的数据就好。这里写图片描述

传入c值表示当前需要的歌词文件是第几个。这样写是为了当用户发现歌词不同时,可以切换歌词。当没有数据或者失败时,返回“”。

  public static String parseJOSNWithGSON(Response response ,int c){
        try{
            String ResponsData = response.body().string();
            JSONObject jsonObject = new JSONObject(ResponsData);
            int count = Integer.parseInt(jsonObject.getString("count"));
            Log.i("TAG", "parseJOSNWithGSONCOUNT:"+count);
            if (count>=c){
                String result = jsonObject.getString("result");
                JSONArray jsonArray = new JSONArray(result);
                JSONObject jsonObject1 = jsonArray.getJSONObject(c-1);
                String url = jsonObject1.getString("lrc");
                Log.i("TAG", "parseJOSNWithGSON:1 "+url);
                return url;
            }else {
                Log.i("TAG", "parseJOSNWithGSON: "+c);
                return "";
            }
        }catch (Exception e){

        }
        return "";

    }
  • 最后根据解析出来的URL读取出歌词,准备解析
    public static String getLrcFromAssets(String Url){
        Log.i("first","getLrcFromAssets: "+Url);
        if (Url.equals("")){
            return "";
        }
        try {
            URL url=new URL(Url);
            HttpURLConnection conn=(HttpURLConnection)url.openConnection();
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            InputStream input=conn.getInputStream();
            BufferedReader in=new BufferedReader(new InputStreamReader(input));
            String line = "" ;
            String result = "";
            while ((line = in.readLine() )!= null){//逐行读取
                if (line.trim().equals(""))
                    continue;
                result += line + "\r\n";
                Log.i("first","getLrcFromAssets: "+result);
            }
            Log.i("total","getLrcFromAssets: "+result);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

到这里我们的歌词就读取出来(此处应有掌声)~

歌词的解析

我们一般的歌词文件分为两种。
第一种:

[ti:流浪的小孩]
[ar:伊能静]
[al:流浪的小孩]
[00:01]流浪的小孩
[00:05]专辑:流浪的小孩
[00:07] 唱: 伊能静
[00:08]流浪的小孩 泪为自己流
[00:15]流浪的小孩 笑发自心中
[00:22]流浪的小孩 少年多挥霍
[00:28]心比世界还宽容
[00:34]MUSIC
[01:06]不断要往那里走 找到一个地方属于我
[01:14]不需要勉强虚伪 心像风一样自由
[01:22]告别金色和无知 不希望自己变得太成熟
[01:30]成人的谎言太多 梦也渐渐被现实夺走
[01:38]我要出去走一走 年少的心有勇气追求
[01:46]看世界多开阔 天能够有多大 梦多难求
[01:54]我要出去走一走 孤单的生活会更快乐
[02:02]也许会有挫折 但这是我的选择
[02:10]流浪的小孩 泪为自己流

第二种:

[00:00.00]海阔天空
[00:15.00]专辑:乐与怒
[00:18.00] [01:43.00][00:19.00]今天我寒夜里看雪飘过
[01:49.00][00:25.00]怀著冷却了的心窝飘远方
[01:55.00][00:31.00]风雨里追赶
[01:58.00][00:34.00]雾里分不清影踪
[02:05.00][00:40.00]可会变(谁没在变)
[00:50.00]从没有放弃过心中的理想
[00:56.00]一刹那恍惚
[01:05.00]心里爱(谁明白我)

第一种只有分和秒,而第二种还包含了毫秒。除此之外,有的时间会对应多句歌词。所以我们在解析前就需要把时间都分开,最后按照时间大小排序 。

  • 新建一个接口
public interface ILrcBulider {
    List<LrcRow> getLrcRows(String rawLrc);
}
  • 然后是解析歌词构造器
    实现上面写的那个接口,得到一个按时间从小到大歌词集合。
public class DefaultLrcBulider implements ILrcBulider {
    @Override
    public List<LrcRow> getLrcRows(final String rawLrc) {
        Log.i(TAG,"getLrcRows by rawString"+rawLrc+"");
        if (rawLrc == null || rawLrc.length() == 0){
            Log.i(TAG,"getLrcRows rawLrc null or empty");
            return null;
        }
        StringReader reader = new StringReader(rawLrc);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line = null;
        List<LrcRow> rows = new ArrayList<LrcRow>();
        try{
            //循环地读取歌词的每一行
            do{
                line = bufferedReader.readLine();
                Log.i(TAG,"lrc raw line: " + line);
                if(line != null && line.length() > 0){
                    //解析每一行歌词 得到每行歌词的集合,因为有些歌词重复有多个时间,就可以解析出多个歌词行来
                    List<LrcRow> lrcRows = LrcRow.createRows(line);
                    if(lrcRows != null && lrcRows.size() > 0){
                        for(LrcRow row : lrcRows){//将每一个LrcRow依次添加
                            rows.add(row);
                        }
                    }
                }
            }while(line != null);

            if( rows.size() > 0 ){
                // 根据歌词行的时间排序
                Collections.sort(rows);
                if(rows!=null&&rows.size()>0){
                    for(LrcRow lrcRow:rows){
                        Log.i(TAG, "lrcRow:" + lrcRow.toString());
                    }
                }
            }
        }catch(Exception e){
            Log.e(TAG,"parse exceptioned:" + e.getMessage());
            return null;
        }finally{
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            reader.close();
        }
        return rows;
    }
}
}

这里插播一下

1.什么是Comparable接口

此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它的自然比较方法
。实现此接口的对象列表(和数组)可以通过 Collections.sort (和 Arrays.sort
)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。 强烈推荐(虽然不是必需的)使自然排序与
equals 一致。所谓与equals一致是指对于类 C 的每一个 e1 和 e2 来说,当且仅当
(e1.compareTo((Object)e2) == 0) 与e1.equals((Object)e2) 具有相同的布尔值时,类 C
的自然排序才叫做与 equals 一致 。

2.实现什么方法

int compareTo(T o) 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。 参数:
o - 要比较的对象。 返回:
负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。 抛出:
ClassCastException - 如果指定对象的类型不允许它与此对象进行比较。

因为我们要实现排序,所以要通过实现Comparable接口来实现。

public class LrcRow implements Comparable<LrcRow>{
    public final static String TAG = "LrcRow";
    /** 该行歌词要开始播放的时间/
    public String strTime;
    /** 该行歌词要开始播放的时间转换为long型,
     * 即将2分34秒14毫秒都转为毫秒后 得到的long型值:time=02*60*1000+34*1000+14
     */
    public long time;
    /** 该行歌词的内容 */
    public String content;

    public LrcRow(){}

    public LrcRow(String strTime,long time,String content){
        this.strTime = strTime;
        this.time = time;
        this.content = content;
    }

    @Override
    public String toString() {
        return "[" + strTime + " ]"  + content;
    }

    /**
     * 读取歌词的每一行内容,转换为LrcRow,加入到集合中
     */
    public static List<LrcRow> createRows(String standardLrcLine){

        try{//判断是否包含为有时间的歌词内容(两种歌词文件都进行判断)
            if (standardLrcLine.indexOf("[") == 0){
                 if (standardLrcLine.indexOf("]") == 6 ||standardLrcLine.indexOf("]") == 9 ){

                 }else{
                    if (standardLrcLine.indexOf(".") == 6){

                    }else {
                        return null;
                    }
                 }
            }else{
                return null ;
            }

            //[02:34.14][01:07.00]当你我不小心又想起她
            //找到最后一个 ‘]’ 的位置
            int lastIndexOfRightBracket = standardLrcLine.lastIndexOf("]");
            //歌词内容就是 ‘]’ 的位置之后的文本   eg:   当你我不小心又想起她
            String content = standardLrcLine.substring(lastIndexOfRightBracket + 1, standardLrcLine.length());
            //歌词时间就是 ‘]’ 的位置之前的文本   eg:   [02:34.14][01:07.00]

            /**
             将时间格式转换一下  [mm:ss.SS][mm:ss.SS] 转换为  -mm:ss.SS--mm:ss.SS-
             即:[02:34.14][01:07.00]  转换为      -02:34.14--01:07.00-
             */
            String times = standardLrcLine.substring(0,lastIndexOfRightBracket + 1).replace("[", "-").replace("]", "-");
            //通过 ‘-’ 来拆分字符串
            String arrTimes[] = times.split("-");
            List<LrcRow> listTimes = new ArrayList<LrcRow>();
            for(String temp : arrTimes){
                if(temp.trim().length() == 0){
                    continue;
                }
                /** [02:34.14][01:07.00]当你我不小心又想起她
                 *
                 上面的歌词的就可以拆分为下面两句歌词了
                 [02:34.14]当你我不小心又想起她
                 [01:07.00]当你我不小心又想起她
                 */
                LrcRow lrcRow = new LrcRow(temp, timeConvert(temp), content);
                listTimes.add(lrcRow);
            }
            return listTimes;
        }catch(Exception e){
            Log.e(TAG,"createRows exception:" + e.getMessage());
            return null;
        }
    }

    /**
     * 将解析得到的表示时间的字符转化为Long型
     */
    private static long timeConvert(String timeString){
        if(timeString.length() == 5){
            timeString =timeString+":00";
        }//如果为第一种歌词文件,则将其转换为第二种。
        //因为给如的字符串的时间格式为XX:XX.XX,返回的long要求是以毫秒为单位
        //将字符串 XX:XX.XX 转换为 XX:XX:XX
        timeString = timeString.replace('.', ':');
        //将字符串 XX:XX:XX 拆分
        String[] times = timeString.split(":");
        // mm:ss:SS
        return Integer.valueOf(times[0]) * 60 * 1000 +//分
                Integer.valueOf(times[1]) * 1000 +//秒
                Integer.valueOf(times[2]) ;//毫秒
    }

    /**
     * 排序的时候,根据歌词的时间来排序
     */
    public int compareTo(LrcRow another) {
        return (int)(this.time - another.time);
    }
}
  • 自定义View

跟着网上大神写的。 https://github.com/ouyangpeng/android-lrc-view-oyp
主要就是根据当前时间去判断应该让哪一句歌词高亮。
通过计时器每秒钟更新当前时间,(我是放在歌词进度条的那个计时器中的,这样可以简洁很多嘛)
当这个时间处于AB两句歌词之间时,就让A高亮。


学习这个虽然用了很多时间,但是收获还是很大的。
路漫漫其修远兮,加油吧。
珍惜这段时间,可以做自己喜欢的事情。
觉得非常幸运~
[爱心]


  • 4
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值