• 安卓 播放MP3 实现歌词同步例子


    哎,敢接触这个东西,看了好些东西,才明白,其中,借鉴如下这位网友: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("
    ", "")
                .replaceAll("
    ", "")
                .trim());
             long secInt = Long.parseLong(sec[0]
                .replaceAll("\D+", "")
                .replaceAll("
    ", "")
                .replaceAll("
    ", "")
                .trim());
             long milInt = Long.parseLong(sec[1]
                .replaceAll("\D+", "")
                .replaceAll("
    ", "")
                .replaceAll("
    ", "")
                .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文件写的,所以不会引起空指针。但是如果在网络情况下,就是根据歌词名去搜索网络,你懂的,有些歌词名不一定规则,搜索不到,就会引起空指针。最好在自定义文件加个判断,判断那些集合或数组非空情况下才画画

  • 相关阅读:
    东方财富炒股公式
    centos8安装MySQL8——通过yum
    官网下载工具时。各种不同后缀名称的区别。
    线上不停机部署mysql主从
    店员任务项目总结
    JS到PHP使用RSA算法进行加密通讯
    无锁同步-JAVA之Volatile、Atomic和CAS
    无锁同步-C++11之Atomic和CAS
    浅谈一个网页打开的全过程(涉及DNS、CDN、Nginx负载均衡等)
    SQLServer数据库卡,内存吃不上去,系统资源用不上
  • 原文地址:https://www.cnblogs.com/share2015/p/5271618.html
Copyright © 2020-2023  润新知