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

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

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);

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

时间: 2024-12-28 04:16:59

手机影音第十七天,实现歌词同步的相关文章

黑马2018年最新kotlin项目实战视频 (手机影音、即时通讯、黑马外卖、坦克大战等)

├─1.kotlin简介和学习方法│ 01_kotlin课程简介_01.mp4│ 02_kotlin学习方法_01.mp4│ 03_kotlin选好教练车_01.mp4│ 03_kotlin选好教练车_02.mp4│ 04_kotlin你好世界_01.mp4│ 05_kotlin变量与输出_01.mp4│ 05_kotlin变量与输出_02.mp4│ ├─10.类,对象和接口(三)│ 38_kotlin面向对象-抽象类和接口_01.mp4│ 38_kotlin面向对象-抽象类和接口_02.mp4

安卓音乐播放器中歌词同步问题

音乐文件是.lrc格式的,以一首歌曲为例, [ti:回忆的沙漏][ar:邓紫棋][al:G.E.M.][by:][offset:0][00:02.50]回忆的沙漏 - G.E.M. 邓紫棋[00:04.62]词:庭竹[00:05.72]曲:G.E.M.[00:15.03]拼图一片片失落[00:18.56]像枫叶的冷漠[00:21.87]墙上的钟[00:23.79]默默数着寂寞[00:29.30]咖啡飘散过香味[00:33.06]剩苦涩陪着我[00:36.68]想念的心[00:39.44]埋葬我在

Android 手机影音 学习过程记录(六)

前一篇已经将音乐播放及切换的相关逻辑弄好了,今天主要理一下剩余的部分,包括: 1. 自定义通知栏的布局及逻辑处理 2. 滚动歌词的绘制 3. 歌词解析 效果图 通知栏 自定义布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layou

我的项目7 js 实现歌词同步(额,小小的效果)

在项目中需要做一个播放器,还要实现歌词同步的效果,就跟现在搜狗音乐的歌词同步差不多,在网上查了一些关于这方面的,整理了一下,在这里,其实用这个方法可以吗? <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript" src="js/jqu

HTML5音频播放,歌词同步,及视频播放功能(JPlayer、JWPlayer、VideoJS)

近期项目中用到音频视频播放.所以就写了一个demo: 这个是JPlayer插件的视频播放: 这个是音频播放,歌词同步: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <!--注意:在IIS上配置的时候,需加入mime映射,否则歌词出不来.在IIS列表中找到"MIME类型".进行加入.lrc类型;.mp4类型文件--> <head>     <

linux 下 python 调用 mplayer 解析歌词同步播放显示

加载同目录同名歌词同步显示 #!/usr/bin/python # -*- coding: utf-8 -*- import sys, os, time, subprocess, re, chardet def load_lrc(lrc_file):     try:         lrc_contains = open(lrc_file, 'rb').read()         encoding = chardet.detect(lrc_contains)['encoding']     

HTML5实现歌词同步

开篇 HTML5的最强大之处莫过于对媒体文件的处理,如利用一个简单的vedio标签就能够实现视频播放.相似地,在HTML5中也有相应的处理音频文件的标签,那就是audio标签 在线Demo audio标签 实现一个audio标签非常easy,相应的html代码例如以下: <audio id="player" src="music/我在人民广场吃炸鸡.mp3" autoplay controls> </audio> 上述代码不须要一行js脚本就

Jplayer歌词同步显示插件

http://blog.csdn.net/wk313753744/article/details/38758317 1.该插件是一个jquery的编写的跟jplayer实现歌词同步的插件,最终效果如图: 2.首先引入jplayer的相关的js库和样式文件. [html] view plaincopy <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link

AS3 歌词同步

这里实例素材: 我们不一样.lrc 我们不一样.mp3 歌词同步其实就是靠lrc文本文件,打开它,可以看到时间点和对应的歌词. 打开lrc内容如下: [ti:我们不一样][ar:大壮][al:][by:错爱QQ][t_time:(04:30)][00:00.00]歌词千寻 www.lrcgc.com[00:01.11]我们不一样[00:05.07]演唱:大壮[00:08.26]词曲:高进[00:11.29]编曲:张亮[00:14.85]缩混:侯春阳[00:20.04][00:34.81]这么多年