Android歌词播放的实现

公司项目最近有需求,要实现一个音乐系统,涉及到一个歌词播放的功能,现将这个实现过程写下来。

做之前先上网查了下相关APP的情况,发现QQ音乐的桌面歌词效果,正是我想要的,显示两行歌词,轮流播放,并显示播放时的过渡效果。


首先分析的出,歌词有两行,那么应该是两个控件来的分别显示,然后通过一系列算法来控制两行歌词交替显示,这样的话,我们要先写出用于显示歌词的控件来。



歌词显示自定义控件

歌词控件应该具备的方法应该有,设置歌词,设置字体颜色,字体大小,播放方法。
播放方法的参数一定要有播放的时长,和一个播放结束完毕的监听,这个监听提供给调用者,这样才可以播放下一句歌词。

我先将整个播放代码贴上来:

/**
     * 每执行一次,都会降计时器清零
     *
     * @param delay  执行前等待时间
     * @param period 循环时间
     */
    public void showLrc(final Activity context, long delay, long period, final ShowListener listener) {
        tvSelect.setVisibility(VISIBLE);
        tvSelect.setWidth(0);
        tvDefault.setText(lrc);
        tvSelect.setText(lrc);

        Log.i("_lrc", "start_openCounter:" + mOpenCounter.get());
        mOpenCounter.addAndGet(1);
        if (period < 100)
            period = 100;
        float speed = period / 100;

        mTimer = new Timer();
        TimerTask task = new TimerTask() {
            public void run() {
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (percent < 100) {
                            percent++;
                        } else {
                            Log.i("_lrc", "end_openCounter:" + mOpenCounter.get());
                            if (mOpenCounter.get() > 0) {
                                mOpenCounter.set(0);
                                percent = 0;
                                listener.showFinish();
                                tvSelect.setVisibility(INVISIBLE);
                                cancel();
                            }
                        }
                        setPercent();
                    }
                });
            }
        };
        mTimer.schedule(task, delay, (long) speed);
    }


代码上有我刚才提到的几个方法,比如设置歌词,设置字体大小,播放等方法,但仔细看下歌词的参数,仔细看第二个参数,这个参数是用于播放前等待时间,因为歌手在唱下一句的时候,会停留一些时间,会喘口气,休息一会。

歌词要播放的时候显示过渡效果的话,就还要做一些处理,这里的实现原理是,用两个不同颜色的TextView重叠着,我们动态设置上面的TextView的宽度,就可以实现我们要的过渡效果了。


解析歌词工具类

歌词显示的控件有了,接下来我们写一个解析歌词的工具类,这个类的作用是传入一个LRC歌词路径,然后将歌词和歌词对应时间用两个数组容器存放着,提供给调用者获取出歌词和对应的时间即可。
LRC歌词是歌词前面会有对应的播放时间,这个时间用中括号括着,我们通过一点点解析算法,将其放到两个容器就好了。
package com.ysbing.lrcshow;

import android.text.TextUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LrcUtil {

    private List<Integer> mTimeList = new ArrayList<>();
    private List<String> mWords = new ArrayList<>();
    private Iterator<Integer> mTimeIterator;
    private Iterator<String> mWordIterator;

    public LrcUtil(File lrcFile) throws FileNotFoundException {
        readLrcFile(lrcFile);
    }

    public boolean hasTime() {
        return mTimeIterator.hasNext();
    }

    public boolean hasWord() {
        return mWordIterator.hasNext();
    }

    public int getTime() {
        return mTimeIterator.next();
    }

    public String getWord() {
        return mWordIterator.next();
    }

    //处理歌词文件
    private void readLrcFile(File file) throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(file);
        InputStreamReader inputStreamReader;
        try {
            inputStreamReader = new InputStreamReader(
                    fileInputStream, "utf-8");
            readLrcStream(inputStreamReader);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void readLrcStream(InputStreamReader inputStreamReader) {
        BufferedReader bufferedReader = new BufferedReader(
                inputStreamReader);
        String s;
        try {
            while ((s = bufferedReader.readLine()) != null) {
                addTimeToList(s);
                if ((s.contains("[ar:")) || (s.contains("[ti:")) || (s.contains("[by:")) || (s.contains("[al:"))) {
//                    s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
                    continue;
                } else {
                    if (TextUtils.isEmpty(s))
                        continue;
                    int startIndex = s.indexOf("[");
                    int endIndex = s.indexOf("]");
                    if (startIndex >= 0 && endIndex >= 0) {
                        String ss = s.substring(startIndex, endIndex + 1);
                        s = s.replace(ss, "");
                    } else continue;
                }
                mWords.add(s);
            }
            mTimeIterator = mTimeList.iterator();
            mWordIterator = mWords.iterator();
            bufferedReader.close();
            inputStreamReader.close();
        } catch (IOException e) {
            e.printStackTrace();
            mWords.add("没有读取到歌词");
        }
    }

    private void addTimeToList(String string) {
        Matcher matcher = Pattern.compile(
                "\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
        if (matcher.find()) {
            String str = matcher.group();
            mTimeList.add(timeHandler(str.substring(1,
                    str.length() - 1)));
        }
    }

    // 分离出时间
    private int timeHandler(String string) {
        string = string.replace(".", ":");
        String timeData[] = string.split(":");
        // 分离出分、秒并转换为整型
        int minute = Integer.parseInt(timeData[0]);
        int second = Integer.parseInt(timeData[1]);
        int millisecond = Integer.parseInt(timeData[2]);
        if (timeData[2].length() == 1)
            millisecond *= 100;
        else if (timeData[2].length() == 2)
            millisecond *= 10;

        // 计算上一行与下一行的时间转换为毫秒数
        return (minute * 60 + second) * 1000 + millisecond;
    }
}

歌词控制

歌词解析工具也有了,接下来要做的事情,就是本节博客的核心,分析如何控制两个歌词的交替显示。
歌词有两行,我们先开始命名,第一行歌词的时间叫“时间一”,歌词叫“歌词一”,第二行则叫“时间二”和“歌词二”。
如果歌词1为空,设置歌词1为下一句歌词,记录时间1-时间2为歌词2的等待时间
歌词1播放->时间2-时间1

我们要考虑到的情况有,在LRC歌词文件中,在歌手喘气休息的时候,是没歌词的,这种情况在LRC中,没歌词,有时间;
假设我们在播放第一行歌词的时候,就有这样的逻辑

如果歌词二为空,就获取下一句歌词作为歌词二,获取下一个时间为时间二

在歌词一播放完后,我们在播放完的回调中,再播放歌词二,这样循环下去,知道歌词结束。

这样我们就实现了歌词的交叉播放,但还存在一个问题,就是,播放的时候是一句接着一句的,如果手机性能比较差,就可能慢慢的,就会和音乐不同了,这个情况也是不允许出现的,所以开始升级。

升级后逻辑是:

记录开始时间
播放前获取现在时间,减去和歌词时间的差
歌词结束时间减去开始播放时间,算出速度

这样总体我们实现完了,效果还可以,就算手机再差,也会在播放下一句中的时候,准确对准歌词的时间。源码我上传到优快云,再去下载看看具体逻辑吧,谢谢观看!


资源下载地址:http://download.youkuaiyun.com/detail/ysb794008002/9586853



评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值