哎,敢接触这个东西,看了好些东西,才明白,其中,借鉴如下这位网友:http://www.cnblogs.com/wenjiang/archive/2013/05/06/3063259.html?utm_source=tuicool

但还是看得很难懂:后来终于搞明白了,特简单易懂地写下来。

首先,如果解析lrc歌词文件:有些歌词是一句接着一句按时间顺序排列好的,但是有些事重复的没有顺序排列的,如下:

[00:00.00]午夜怨曲
[00:09.00]词 \ 叶世荣.   曲 \ 黄家驹.  主唱 \ 黄家驹.
[00:18.00]
[00:27.00]从来不知想拥有多少的理想
[00:33.00]还离不开种种困忧
[00:40.00]勉强去掩饰失意的感觉
[00:46.00]再次听到昨日的冷嘲
[00:52.00]徘徊於街中恐怕只得孤独
[00:58.00]寻回思忆中的碎片
[01:05.00]变作了一堆草芥风中散
[01:11.00]与你奏过午夜的怨曲
[04:12.00][03:47.00][03:22.00][02:19.00][01:16.00]总有挫折打碎我的心
[04:16.00][03:51.00][03:25.00][02:22.00][01:19.00]紧抱过去抑压了的手
[04:20.00][03:55.00][03:30.00][02:27.00][01:23.00]我与你也彼此一起艰苦过
[04:00.00][03:35.00][02:32.00][01:28.00]写上每句冰冷冷的诗
[04:03.00][03:38.00][02:35.00][01:31.00]不会放弃高唱这首歌
[04:07.00][03:42.00][02:39.00][01:36.00]我与你也彼此真的相识过
[01:55.00]从回忆中找不到天真的笑声
[02:01.00]曾留不低心中斗争
[02:08.00]每次去担当失意的主角
[02:14.00]冷笑变作故事的作者
[03:11.00]啊......啊......障碍能撕破
[04:25.00]
[04:27.00]BEYOND再见理想
[04:29.00]/~byfaith

但是解析也是差不多而已,只不过,找个稍微多用力一点点,

首选,建立一个map存放上面的有时间的内容;开头这些信息也没什么用,另放一个,有用则用,没用则不用;因为map存放时无序的,于是乎建立一个数组存放把key排序好的时间数组:

<span style="white-space:pre">  </span>//存放开头那些没有时间的信息private List<String> info = new ArrayList<String>();//存放有时间的歌词private Map<String, String> lrcs = new Hashtable<String, String>();//存放按照从小到大排序好的时间信息private Object[] arr;

然后写一个主要运用indexof、substring来截取文本的方法:

//获取歌词,放进集合中public void decodeLrc(String str) {if (str.startsWith("[ti:")) {info.add(str.substring(4, str.lastIndexOf("]")));} else if (str.startsWith("[ar:")) {info.add(str.substring(4, str.lastIndexOf("]")));} else if (str.startsWith("[al:")) {info.add(str.substring(4, str.lastIndexOf("]")));} else if (str.startsWith("[la:")) {info.add(str.substring(4, str.lastIndexOf("]")));} else if (str.startsWith("[by:")) {info.add(str.substring(4, str.lastIndexOf("]")));} else {//这里获取歌词信息int startIndex;int tempIndex = -1;//获取多个中括号的相同歌词的信息while ((startIndex = str.indexOf("[", tempIndex + 1)) != -1) {int endIndex = str.indexOf("]", tempIndex + 1);String tempTime = str.substring(tempIndex + 2, endIndex);lrcs.put(tempTime, str.substring(str.lastIndexOf("]") + 1, str.length()));tempIndex = endIndex;}}}

通过上面这个方法,就把数据各自存放好了。接着写一个把hashtable的key键,也就是“时间”排序好,存放于arr数组中

<span style="white-space:pre">  </span>//把hashtable转为有秩序的组合public void convertArrays() {arr = lrcs.keySet().toArray();Arrays.sort(arr);}

================================================================================================================================

以上就是从lrc文件中获取歌词信息的内容。但往回看,如果读取lrc内,这样也就是一个方法:通过这个方法读取文件,然后再用上面的解析......

private void getLrcs(InputStreamReader isr) {try {BufferedReader reader = new BufferedReader(isr);String line = "";while ((line = reader.readLine()) != null) {//一行一行读取,到方法中去解析信息decodeLrc(line);}//key键的时间排序、组合convertArrays();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

===========================================================================================================

歌词信息准备好了,接着就是写一个自定义的View来显示歌词同步,这个原理大概就是这样的:假设mediaplyaer传过来的时间是X,好了,我们把上面数组中的时间转为long型,假设第一句高亮歌词是Y,和这个X作比较,如果X大于或者等于Y,那么我们就开始显示下一行的高亮(上面已经做了歌词的排序是吧),通过重绘更新UI。

整个View的代码如下:

package china.testwt;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import java.io.*;
import java.util.*;

public class MyView extends View {

   private int mBgCol, mCurTextCol, mNorTextCol;
   private int mCurrentTextSize, mNormalTextSize;

   //存放开头那些没有时间的信息
   private List<String> info = new ArrayList<String>();
   //存放有时间的歌词
   private Map<String, String> lrcs = new Hashtable<String, String>();
   //存放按照从小到大排序好的时间信息
   private Object[] arr;

   //不高亮的歌词画笔
   private Paint mLoseFocusPaint;
   //高亮的
   private Paint mOnFocusePaint;
   //一行歌词的开始位置X
   private float drawTextX = 0;
   //Y
   private float drawTextY = 0;
   //整个View的高
   private float viewHeight = 0;
   //间隔,移动的大小
   private int mSpacing;
   //高亮的行数
   private int mIndex = 0;

   //获取数据源,接口
   public void setLrcSource(FileInputStream reader) {
      InputStreamReader isr = new InputStreamReader(reader);
      getLrcs(isr);
   }

   //获取当前行位置,接口
   public void setLrcPostion(long position) {

      if (mIndex < lrcs.size()) {

         long tmepPos = parseTime(arr[mIndex].toString());

         if (tmepPos < position) {

            //更新UI
            postInvalidate();
         }
      }

   }

   //构造方法
   public MyView(Context context) {
      this(context, null);
   }

   public MyView(Context context, AttributeSet attrs) {
      this(context, attrs, 0);
   }

   public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);

      //以下关联好各种自定义属性
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);

      mBgCol = a.getColor(R.styleable.MyView_bgColor, Color.GREEN);
      mCurTextCol = a.getColor(R.styleable.MyView_CurTextColor, Color.YELLOW);
      mNorTextCol = a.getColor(R.styleable.MyView_NorTextColor, Color.WHITE);
      mCurrentTextSize = a.getDimensionPixelSize(R.styleable.MyView_currentTextSize, 28);
      mNormalTextSize = a.getDimensionPixelSize(R.styleable.MyView_normalTextSize, 24);

      a.recycle();

      mLoseFocusPaint = new Paint();
      mLoseFocusPaint.setAntiAlias(true);
      mLoseFocusPaint.setTextSize(mNormalTextSize);
      mLoseFocusPaint.setColor(mNorTextCol);
      mLoseFocusPaint.setTypeface(Typeface.SERIF);

      mOnFocusePaint = new Paint();
      mOnFocusePaint.setAntiAlias(true);
      mOnFocusePaint.setColor(mCurTextCol);
      mOnFocusePaint.setTextSize(mCurrentTextSize);
      mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);

   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      //非wrap_content 用默认的
   }

   //这个方法运行了才运行ondraw()
   @Override
   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
      super.onSizeChanged(w, h, oldw, oldh);

      //从中间开始,而且歌词居中。在下面设置了
      drawTextX = w * 0.5f;
      //高就是高了
      viewHeight = h;
      //从0.3高度的地方开始画
      drawTextY = h * 0.3f;

   }

   @Override
   protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);

      //话背景
      canvas.drawColor(mBgCol);

      //居中设置
      Paint p = mLoseFocusPaint;
      p.setTextAlign(Paint.Align.CENTER);
      Paint p2 = mOnFocusePaint;
      p2.setTextAlign(Paint.Align.CENTER);

      //间隔,为文字大小加上10个像素
      mSpacing = mCurrentTextSize + 10;

      //画高亮的
      canvas.drawText(lrcs.get(arr[mIndex]), drawTextX, drawTextY, p2);

      //画高亮上面的歌词,高度递减,透明度递减
      int alphaValue = 25;
      float tempY = drawTextY;
      for (int i = mIndex - 1; i >= 0; i--) {
         tempY -= mSpacing;
         if (tempY < 0) {
            break;
         }
         p.setColor(Color.argb(255 - alphaValue, 255, 255, 255));
         canvas.drawText(lrcs.get(arr[i]), drawTextX, tempY, p);
         alphaValue += 25;
      }

      //画高亮下面的歌词,高度递增,透明度递减
      alphaValue = 25;
      tempY = drawTextY;
      for (int i = mIndex + 1; i < lrcs.size(); i++) {

         tempY += mSpacing;
         //超出不显示啦
         if (tempY > viewHeight) {
            break;
         }

         p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
         canvas.drawText(lrcs.get(arr[i]), drawTextX, tempY, p);

         //如果没超出就达到了100%透明,往后的都100%透明
         if (alphaValue + 25 > 255) {

            alphaValue = 255;

         } else {

            alphaValue += 25;
         }
      }

      //准备下一行刷新,重绘,这有赖于传进来的时间对比

      mIndex++;
   }

   private void getLrcs(InputStreamReader isr) {

      try {

         BufferedReader reader = new BufferedReader(isr);
         String line = "";

         while ((line = reader.readLine()) != null) {

            //一行一行读取,到方法中去解析信息
            decodeLrc(line);

         }

         //key键的时间排序、组合
         convertArrays();

      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }

   }

   //获取歌词,放进集合中
   public void decodeLrc(String str) {

      if (str.startsWith("[ti:")) {
         info.add(str.substring(4, str.lastIndexOf("]")));

      } else if (str.startsWith("[ar:")) {

         info.add(str.substring(4, str.lastIndexOf("]")));

      } else if (str.startsWith("[al:")) {

         info.add(str.substring(4, str.lastIndexOf("]")));
      } else if (str.startsWith("[la:")) {
         info.add(str.substring(4, str.lastIndexOf("]")));

      } else if (str.startsWith("[by:")) {
         info.add(str.substring(4, str.lastIndexOf("]")));

      } else {

         //这里获取歌词信息
         int startIndex;
         int tempIndex = -1;
         //获取多个中括号的相同歌词的信息
         while ((startIndex = str.indexOf("[", tempIndex + 1)) != -1) {

            int endIndex = str.indexOf("]", tempIndex + 1);
            String tempTime = str.substring(tempIndex + 2, endIndex);
            lrcs.put(tempTime, str.substring(str.lastIndexOf("]") + 1, str.length()));

            tempIndex = endIndex;

         }
      }
   }

   // 解析时间,把时间转为long
   @Nullable
   private Long parseTime(String time) {
      // 03:02.12
      if (time.indexOf(":") != -1) {

         String[] min = time.split(":");
         String[] sec = min[1].split("\\.");

         long minInt = Long.parseLong(min[0]
            .replaceAll("\\D+", "")
            .replaceAll("\r", "")
            .replaceAll("\n", "")
            .trim());
         long secInt = Long.parseLong(sec[0]
            .replaceAll("\\D+", "")
            .replaceAll("\r", "")
            .replaceAll("\n", "")
            .trim());
         long milInt = Long.parseLong(sec[1]
            .replaceAll("\\D+", "")
            .replaceAll("\r", "")
            .replaceAll("\n", "")
            .trim());

         return minInt * 60 * 1000 + secInt * 1000 + milInt * 10;
      } else {

         return null;

      }

   }

   //把hashtable转为有秩序的组合
   public void convertArrays() {
      arr = lrcs.keySet().toArray();
      Arrays.sort(arr);
   }
}

mainActivity是这样的:

package china.testwt;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import butterknife.Bind;
import butterknife.ButterKnife;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class MainActivity extends AppCompatActivity {

   private static final String path = "/storage/emulated/0/Music/Beyond - 午夜怨曲.mp3";
   @Bind(R.id.songs)
   china.testwt.MyView songs;

   private MediaPlayer mPlayer;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      ButterKnife.bind(this);

      mPlayer = new MediaPlayer();

      try {
         //数据源
         songs.setLrcSource(new FileInputStream(new File("/storage/emulated/0/Music/Beyond - 午夜怨曲.lrc")));

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

      try {
         mPlayer.setDataSource(path);
         mPlayer.setOnPreparedListener(new PreparedListener());
         mPlayer.prepareAsync();

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

   private class PreparedListener implements MediaPlayer.OnPreparedListener {
      @Override
      public void onPrepared(MediaPlayer mp) {

         mPlayer.start();

         new Thread(new Runnable() {
            @Override
            public void run() {

               while (mPlayer.isPlaying()) {

                  //传进去进度
                  songs.setLrcPostion(mPlayer.getCurrentPosition());

                  try {
                     //睡觉
                     Thread.sleep(100);
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
               }
            }
         }).start();
      }
   }

}

xml是这样的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="china.testwt.MainActivity">

    <china.testwt.MyView
            android:id="@+id/songs"
            app:normalTextSize="25dp"
            app:currentTextSize="30dp"
            app:bgColor="@color/colorPrimary"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</LinearLayout>

PS,因为上面是基于本地lrc文件写的,所以不会引起空指针。但是如果在网络情况下,就是根据歌词名去搜索网络,你懂的,有些歌词名不一定规则,搜索不到,就会引起空指针。最好在自定义文件加个判断,判断那些集合或数组非空情况下才画画

安卓 播放MP3 实现歌词同步例子相关推荐

  1. 网页播放技术的歌词同步

    网页播放技术的歌词同步 字体:  小  中  大  | 打印 发布: 2007-8-08 06:05    作者: bc8    来源: webjx.com整理     查看: 4次 WMP 与 Re ...

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

    音乐文件是.lrc格式的,lrc格式的文件,是MP3播放器唯一能识别的歌词文件,在MP3播放器中可以去同步 显示歌词.它是一种包含着"[]"形式的"标签"的.基 ...

  3. android 歌词同步代码,android手机音乐播放器实现歌词同步

    最近在做一款android手机上的音乐播放器,学习到了很多东西,像是Fragment,ActionBar的使用等等,这里就先介绍一下歌词同步的实现问题. 歌词同步的实现思路很简单:获取歌词文件LRC中 ...

  4. 音乐播放器的歌词同步实现

    首先,要下载对应的歌词Lrc文件.因为对应的lrc文件中有固定格式,如:[00:00.00]荷塘月色.前面是时间,可以通过字符串操作得到,进而转换为毫秒数或者其他格式. 我的想法: 1>先创建一 ...

  5. 音乐播放器实现歌词同步

    千千静听的歌词格式如下 [04:02.54][03:33.11][02:17.15][01:29.03]等 你 爱 我 爱 我 [00:26.86]等 你 爱 我 [03:40.75][02:24.7 ...

  6. 基于IOS音乐播放器在线歌词同步小程序系统(音乐小程序)

    目 录 目 录 1 摘 要 3 Abstract 4 1 导论 6 1.1 背景问题 6 1.2 选题意义 6 1.3 本文内容 7 2 核心功能 9 2.1 功能调研 9 2.2 可行性分析 12 ...

  7. HTML5实践之歌词同步播放器

    歌曲播放我们会发现他的兼容性不是很好,譬如IE上能播放的flash播放器,再firfox或者chrome上就不是很好的应用了,因为有插件的阻碍!HTML5的出现让这一切成为了可能,但是播放器虽然播放了 ...

  8. mysql ubb html_UBB中轻松实现歌词同步播放_html

    Windows Media Player 格式: 1.[wmp=http://homepage.yesky.com/a.wmv]歌词内容[/wmp] 2.[wmp=http://homepage.ye ...

  9. html+js的lrc歌词同步播放器

    <html> <head> <title>lrc歌词同步播放器</title> <style> body, td { font-family ...

  10. Android应用开发--MP3音乐播放器滚动歌词实现

    Android应用开发--MP3音乐播放器滚动歌词实现 2013年6月2日  简.美音乐播放器开发记录 -----前话 有网友给我博客评论说,让我借鉴好的Android代码,代码贴出来的时候最好整体先 ...

最新文章

  1. [BZOJ 2054]疯狂的馒头(并查集)
  2. 罗田用好“大数据”力促扶贫更精准
  3. 统一建模语言UML要点全面简析
  4. 电脑怎么打印文件步骤_电脑中毒后怎么办 电脑中毒后解决方法【详细步骤】...
  5. ps、grep和kill联合使用杀掉进程(转)
  6. 谷歌秋季新品发布会即将召开 Pixel 4系列将正式亮相
  7. Lock锁的简单使用
  8. AI一周热闻:北大建立人工智能新校区;英国首例机器人心脏手术致死
  9. Zabbix Agent端配置文件说明
  10. mysql开启中继日志,MySQL复制应用中继日志解析
  11. 如何提升数据分析的效率
  12. ubuntu docker-compose: command not found
  13. [转]刚成为程序员的你需要什么技能
  14. 邮件营销如何“爆增”潜在客户?
  15. js实现地图四级联动
  16. 计算机连校园网没有弹出页面,校园网连接之后CMCC登录界面不能弹出怎么解决?...
  17. 使用 java.lang.Math 类完成编程
  18. 【小程序】一文带你了解微信小程序开发(小程序注册/开发工具的下载)
  19. GEE实战 | 计算NDVI NDWI
  20. 手机ssh发送文件到服务器,使用ssh传输文件

热门文章

  1. shell输出标准时间格式
  2. 管理学研究中应用计算机仿真,计算机仿真在企业流程再造中应用研究.doc
  3. 2020电信校园卡已经发售,更新校园卡最新消息及选购建议
  4. 微信小程序学习总结(旋转音乐盒)
  5. python close函数_Python math.isclose() 方法
  6. div 中进行左右分离
  7. Python爬虫:新浪新闻详情页的数据抓取(函数版)
  8. APS自动排产 — 排产结果拉动物料需求计划
  9. 知乎spark与hadoop讨论
  10. SaaS到底是什么东西