手机影音第十七天,实现歌词同步

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手机影音第十七天,实现歌词同步相关的知识,希望对你有一定的参考价值。

        代码已托管到码云,有兴趣的小伙伴可以下载看看

            https://git.oschina.net/joy_yuan/MobilePlayer


效果图:

    技术分享

        有一个小的遗憾,就是该MP3文件和歌词文件要在同一路径下,才能读取到歌词,否则读取不到录音文件。

    将录音文件发到这里,是.lrc格式的文件,其实TXT文件的也行;如果在手机上显示是乱码的话,就改一下文件的编码为Unicode,再尝试下。

[ti:北京北京]
[00:00.05]献给我最爱的老婆 --常长丽
[00:02.17]歌曲名:北京北京
[00:04.00]演唱:汪峰
[00:06.84]原立鹏 制
[00:08.62] 献给我最爱的老婆 --常长丽
[00:31.16]当我走在这里的每一条街道
[00:37.32]我的心似乎从来都不能平静
[00:45.23]就让花朵妒忌红名和电气致意
[00:51.66]我似乎听到了他这不慢的心跳
[00:59.74]我在这里欢笑我在这里哭泣
[01:06.93]我在这里活着也在这死去
[01:14.09]我在这里祈祷 我在这里迷惘
[01:21.25]我在这里寻找 在这里寻求
[04:11.76][04:04.59][02:31.70][01:27.19]北京 北京
[01:35.46]咖啡管与广场又散着天气
[01:41.73]就象夜空的哪月亮的距离
[01:49.80]人们在挣扎中相互告慰和拥抱
[01:56.14]寻找着 追著着 夜夜时的睡梦
[02:04.19]我在这欢笑 我们在这哭泣
[02:11.23]我在这活着也在这死去
[02:18.70]我在这祈祷 我在这迷惘
[02:25.76]我在这寻找 在这追求
[03:08.56]如果有一天我不得不离去
[03:14.55]我希望人们把我埋葬在这里
[03:22.95]在这忘了感觉到我在存在
[03:29.10]在这有太多有我眷恋的东西
[03:37.38]我在这欢笑 我在这哭泣
[03:44.55]我在这里活着也在这死去
[03:51.74]我在这里祈祷 我在这里迷惘
[03:58.90]我在这里寻找 也在这死去
[04:04.35]北京 北京 
[04:11.48]北京 北京

    1、新建一个解析歌词的类,接收一个File类型的文件,由于解析的类太复杂,就不在这里分析,直接贴源码,大家以后用的时候直接参考即可

package com.yuanlp.mobileplayer.utils;

import com.yuanlp.mobileplayer.bean.Lyrc;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Created by 原立鹏 on 2017/7/31.
 * 解析歌词工具类
 */

public class LyrcUtils {


    /**
     * 得到解析好的歌词列表
     * @return
     */
    public ArrayList<Lyrc> getLyrcs() {
        return Lyrcs;
    }

    private ArrayList<Lyrc> Lyrcs;

    /**
     * 是否存在歌词
     * @return
     */
    public boolean isExistsLyrc() {
        return isExistsLyrc;
    }

    /**
     * 是否存在歌词

     */
    private boolean isExistsLyrc  = false;

    /**
     * 读取歌词文件
     * @param file /mnt/scard/audio/beijingbeijing.txt
     */
    public void readLyrcFile(File file){
        if(file == null || !file.exists()){
            //歌词文件不存在
            Lyrcs = null;
            isExistsLyrc = false;
        }else{
            //歌词文件存在
            //1.解析歌词 一行的读取-解析
            Lyrcs = new ArrayList<>();
            isExistsLyrc = true;
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),getCharset(file)));

                String line = "";
                while ((line = reader.readLine())!= null){
                    line = parsedLyrc(line);//
                }

                reader.close();


            } catch (Exception e) {
                e.printStackTrace();
            }


            //2.排序
            Collections.sort(Lyrcs, new Comparator<Lyrc>() {
                @Override
                public int compare(Lyrc lhs, Lyrc rhs) {
                    if(lhs.getTimePosition() < rhs.getTimePosition()){
                        return  -1;
                    }else if(lhs.getTimePosition() > rhs.getTimePosition()){
                        return  1;
                    }else{
                        return 0;
                    }

                }
            });

            //3.计算每句高亮显示的时间
            for(int i=0;i<Lyrcs.size();i++){
                Lyrc oneLyrc = Lyrcs.get(i);
                if(i+1 < Lyrcs.size()){
                    Lyrc twoLyrc = Lyrcs.get(i+1);
                    oneLyrc.setSleepTime(twoLyrc.getTimePosition()-oneLyrc.getTimePosition());
                }
            }
        }

    }


    /**
     * 判断文件编码
     * @param file 文件
     * @return 编码:GBK,UTF-8,UTF-16LE
     */
    public String getCharset(File file) {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        try {
            boolean checked = false;
            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));
            bis.mark(0);
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1)
                return charset;
            if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE
                    && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF
                    && first3Bytes[1] == (byte) 0xBB
                    && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8";
                checked = true;
            }
            bis.reset();
            if (!checked) {
                int loc = 0;
                while ((read = bis.read()) != -1) {
                    loc++;
                    if (read >= 0xF0)
                        break;
                    if (0x80 <= read && read <= 0xBF)
                        break;
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF)
                            continue;
                        else
                            break;
                    } else if (0xE0 <= read && read <= 0xEF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else
                                break;
                        } else
                            break;
                    }
                }
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return charset;
    }

    /**
     * 解析一句歌词
     * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑
     * @return
     */
    private String parsedLyrc(String line) {
        ////indexOf第一次出现[的位置
        int pos1 = line.indexOf("[");//0,如果没有返回-1

        int pos2 = line.indexOf("]");//9,如果没有返回-1

        if(pos1 ==0 && pos2 != -1){//肯定是由一句歌词

            //装时间
            long[] times = new long[getCountTag(line)];

            String strTime =line.substring(pos1+1,pos2) ;//02:04.12
            times[0] = strTime2LongTime(strTime);

            String content = line;
            int i = 1;
            while (pos1 ==0 && pos2 != -1){
                content = content.substring(pos2 + 1); //[03:37.32][00:59.73]我在这里欢笑--->[00:59.73]我在这里欢笑-->我在这里欢笑
                pos1 = content.indexOf("[");//0/-1
                pos2 = content.indexOf("]");//9//-1

                if(pos2 != -1 ){
                    strTime = content.substring(pos1 + 1, pos2);//03:37.32-->00:59.73
                    times[i] = strTime2LongTime(strTime);

                    if(times[i] == -1){
                        return  "";
                    }

                    i++;
                }

            }

            Lyrc Lyrc = new Lyrc();
            //把时间数组和文本关联起来,并且加入到集合中
            for(int j = 0;j < times.length;j++){

                if(times[j] !=0){//有时间戳

                    Lyrc.setContent(content);
                    Lyrc.setTimePosition(times[j]);
                    //添加到集合中
                    Lyrcs.add(Lyrc);
                    Lyrc = new Lyrc();

                }


            }

            return  content;//我在这里欢笑


        }


        return "";
    }

    /**
     * 把String类型是时间转换成long类型
     * @param strTime 02:04.12
     * @return
     */
    private long strTime2LongTime(String strTime) {
        long result = -1;
        try{

            //1.把02:04.12按照:切割成02和04.12
            String[] s1 = strTime.split(":");
            //2.把04.12按照.切割成04和12
            String[] s2 = s1[1].split("\\.");

            //1.分
            long min = Long.parseLong(s1[0]);

            //2.秒
            long second = Long.parseLong(s2[0]);

            //3.毫秒
            long mil = Long.parseLong(s2[1]);

            result =  min * 60 * 1000 + second * 1000 + mil*10;
        }catch (Exception e){
            e.printStackTrace();
            result = -1;
        }

        return result;
    }

    /**
     * 判断有多少句歌词
     * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑
     * @return
     */
    private int getCountTag(String line) {
        int result = -1;
        String [] left = line.split("\\[");
        String [] right = line.split("\\]");

        if(left.length==0 && right.length ==0){
            result = 1;
        }else if(left.length > right.length){
            result = left.length;
        }else{
            result = right.length;
        }
        return result;
    }
}

2、在audioplayerActivity里,接收到service发送的EventBus的准备好播放的事件后,获取该MP3 文件的路径,最后获取歌词并解析,然后发送到handler里来显示歌词

/**
 * 订阅eventbus
 */
@Subscribe(threadMode=ThreadMode.MAIN,sticky = false,priority = 99)
public void showData(MediaItem item) {

    showLyrc();  //去获取MP3文件的路径然后获取歌词文件,并解析歌词,最终显示歌词
    showViewData();
    checkPlayMode();
}

/**
 * 去获取MP3文件的路径然后获取歌词文件,并解析歌词,最终显示歌词
 */
public void showLyrc() {
    LyrcUtils lyrcUtils=new LyrcUtils();
    try {
        //得到录音文件的地址
        String path=mservice.getAudioPath();
        path=path.substring(0,path.lastIndexOf("."));
        File file=new File(path+".lrc");  //先去查找lrc格式的歌词文件
        if (!file.exists()){
            file=new File(path+".txt");  //拼一个TXT格式的歌词文件
        }
        lyrcUtils.readLyrcFile(file);
        showlyrcView.setLyrcs(lyrcUtils.getLyrcs());
        if (lyrcUtils.isExistsLyrc()){
            //存在歌词,才发消息,否则不发消息
            handler.sendEmptyMessage(SHOW_LYRC);
        }
    } catch (RemoteException e) {
        e.printStackTrace();
    }


}

最终实现了歌词的解析。



这样实现的歌词,匹配到不同的分辨率的手机上时,字的大小会不同,那么下面就是来解决这个显示字大小的修改,最终效果如下。

技术分享

    定义一个工具类,DensityUtil,里面代码如下:

    /**
     * 根据手机的分辨率从 dip 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);  
    }


    那么我们只需要在各自文字设置那里,通过这个工具类,修改下文字大小即可。

    在ShowlyrcView类里,把textHeight=DensityUtil.dip2px(context,20);

    

    然后2个画笔处,修改文字大小即可

    

//创建画笔----当前高亮的画笔
paint=new Paint();
paint.setColor(Color.GREEN); //高亮颜色
paint.setTextSize(DensityUtil.dip2px(context,20));
paint.setAntiAlias(true); //抗锯齿
paint.setTextAlign(Paint.Align.CENTER);  //对齐方式,居中显示

//白色画笔
whitepaint=new Paint();
whitepaint.setColor(Color.WHITE);
whitepaint.setTextAlign(Paint.Align.CENTER);
whitepaint.setTextSize(DensityUtil.dip2px(context,20));
whitepaint.setAntiAlias(true);

最终达到了将歌词匹配屏幕的大小。

本文出自 “YuanGuShi” 博客,请务必保留此出处http://cm0425.blog.51cto.com/10819451/1952517

以上是关于手机影音第十七天,实现歌词同步的主要内容,如果未能解决你的问题,请参考以下文章

学习python第十七天,文件处理

第十七天学习笔记

javaSE第十七天

手机影音第七天 视频的播放下一个视频功能实现,视频进度电量变化的实现

2018.10.11python学习第十七天

Python初学者第十七天 函数