zoukankan      html  css  js  c++  java
  • ANDROID_MARS学习笔记_S01原始版_023_MP3PLAYER004_同步显示歌词

    一、流程分析

    1.点击播放按钮,会根据lrc名调用LrcProcessor的process()分析歌词文件,得到时间队列和歌词队列

    2.new一个hander,把时间队列和歌词队列传给自定义的线程类UpdateTimeCallback,调用handler.postDelayed(updateTimeCallback, 5);启动线程

    3.UpdateTimeCallback会在线程执行时用当前时间减去成员变量begin,则可知歌曲播放了多久,再根据此时间与时间队列的时间比较,就是知道此时要显示什么歌词,从而把歌词队列的一个message设置给lrcTextView以显示

    4.UpdateTimeCallback最后会自己调用handler.postDelayed(updateTimeCallback, 100);,所以线程会每0.1秒判断一次歌词的显示

    PS:此代码有一个不足之处,即使app后台播放,更新歌词的线程仍会执行,浪费资源,一个版本会通过broastreciever来解决此问题

    二、简介

    在linux用apk处理歌词

    三、代码
    1.xml

    2.java
    (1)PlayerActivity.java

      1 package tony.mp3player;
      2 
      3 import java.io.File;
      4 import java.io.FileInputStream;
      5 import java.io.InputStream;
      6 import java.util.List;
      7 import java.util.Queue;
      8 
      9 import tony.model.Mp3Info;
     10 import tony.mp3player.service.PlayerService;
     11 import android.app.Activity;
     12 import android.content.Intent;
     13 import android.os.Bundle;
     14 import android.os.Environment;
     15 import android.os.Handler;
     16 import android.view.View;
     17 import android.view.View.OnClickListener;
     18 import android.widget.ImageButton;
     19 import android.widget.TextView;
     20 
     21 public class PlayerActivity extends Activity {
     22 
     23 
     24     private ImageButton beginBtn = null;
     25     private ImageButton pauseBtn = null;
     26     private ImageButton stopBtn = null;
     27     
     28     private List<Queue> queues = null;
     29     private TextView lrcTextView = null;
     30     private Mp3Info info = null;
     31     private Handler handler = new Handler();
     32     private UpdateTimeCallback updateTimeCallback = null;
     33     private long begin = 0;
     34     private long nextTimeMill = 0;
     35     private long currentTimeMill = 0;
     36     private String msg = null;
     37     private long pauseTimeMills = 0;
     38     private boolean isPlaying = false;
     39     
     40     @Override
     41     protected void onCreate(Bundle savedInstanceState) {
     42         super.onCreate(savedInstanceState);
     43         setContentView(R.layout.player);
     44         Intent intent = getIntent();
     45         info = (Mp3Info) intent.getSerializableExtra("mp3Info");
     46         beginBtn = (ImageButton) findViewById(R.id.begin);
     47         pauseBtn = (ImageButton) findViewById(R.id.pause);
     48         stopBtn = (ImageButton) findViewById(R.id.stop);
     49         lrcTextView = (TextView) findViewById(R.id.lrcText);
     50         
     51         beginBtn.setOnClickListener(new BeginListener());
     52         pauseBtn.setOnClickListener(new PauseListener());
     53         stopBtn.setOnClickListener(new StopListener());
     54     }
     55     
     56     /**
     57      * 根据歌词文件的名字,来读取歌词文件当中的信息
     58      * @param lrcName
     59      */
     60     private void prepareLrc(String lrcName) {
     61         try {
     62             InputStream inputStream;
     63             inputStream = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + 
     64                     File.separator + "mp3" + File.separator + info.getLrcName());
     65             LrcProcessor lrcProcessor = new LrcProcessor();
     66             queues = lrcProcessor.process(inputStream);
     67             updateTimeCallback = new UpdateTimeCallback(queues);
     68             begin = 0;
     69             currentTimeMill = 0;
     70             nextTimeMill = 0;
     71         } catch (Exception e) {
     72             e.printStackTrace();
     73         }
     74     }
     75     
     76     class BeginListener implements OnClickListener {
     77         @Override
     78         public void onClick(View v) {
     79             if(!isPlaying) {
     80                 //创建一个Intent对象,用于通知Service开始播放MP3
     81                 Intent intent = new Intent();
     82                 intent.putExtra("mp3Info", info);
     83                 intent.putExtra("MSG", AppConstant.PlayerMsg.PLAY_MSG);
     84                 intent.setClass(PlayerActivity.this, PlayerService.class);
     85                 //读取LRC文件,放于startservice前,是为了防止歌曲已播放,但歌词没读完,造成不同步
     86                 prepareLrc(info.getLrcName());
     87                 startService(intent);
     88                 begin = System.currentTimeMillis();
     89                 handler = new Handler();
     90                 handler.postDelayed(updateTimeCallback, 5);//5毫秒是试验得出的
     91                 isPlaying = true;
     92             }
     93         }
     94     }
     95     
     96     class PauseListener implements OnClickListener {
     97         @Override
     98         public void onClick(View v) {
     99             //通知Service暂停播放MP3
    100             Intent intent = new Intent();
    101             intent.putExtra("MSG", AppConstant.PlayerMsg.PAUSE_MSG);
    102             intent.setClass(PlayerActivity.this, PlayerService.class);
    103             startService(intent);
    104             if(isPlaying) {
    105                 //不再更新歌词
    106                 handler.removeCallbacks(updateTimeCallback);
    107                 //用来下面代码计算暂停了多久
    108                 pauseTimeMills = System.currentTimeMillis();
    109             } else {
    110                 handler.postDelayed(updateTimeCallback, 5);
    111                 //因为下面的时间偏移是这样计算的offset = System.currentTimeMillis() - begin;
    112                 //所以要把暂停的时间加到begin里去,
    113                 begin = System.currentTimeMillis() - pauseTimeMills + begin;
    114             }
    115             isPlaying = !isPlaying;
    116         }
    117     }
    118     
    119     class StopListener implements OnClickListener {
    120         @Override
    121         public void onClick(View v) {
    122             //通知Service停止播放MP3文件
    123             Intent intent = new Intent();
    124             intent.putExtra("MSG", AppConstant.PlayerMsg.STOP_MSG);
    125             intent.setClass(PlayerActivity.this, PlayerService.class);
    126             startService(intent);
    127             //从Handler当中移除updateTimeCallback
    128             handler.removeCallbacks(updateTimeCallback);
    129             isPlaying = false;
    130         }
    131     }
    132     
    133     class UpdateTimeCallback implements Runnable{
    134         Queue<Long> times = null;
    135         Queue<String> msgs = null;
    136         
    137         public UpdateTimeCallback(List<Queue> queues) {
    138             this.times = queues.get(0);
    139             this.msgs = queues.get(1);
    140         }
    141 
    142         @Override
    143         public void run() {
    144             //计算偏移量,也就是说从开始播放MP3到现在为止,共消耗了多少时间,以毫秒为单位
    145             long offset = System.currentTimeMillis() - begin;
    146             if(currentTimeMill == 0) {//刚开始播放时,调用prepareLrc(),在其中设置currentTimeMill=0
    147                 nextTimeMill = times.poll();
    148                 msg = msgs.poll();
    149             }
    150             //歌词的显示是如下:例如
    151             //[00:01.00]Look
    152             //[00:03.00]Up
    153             //[00:06.00]Down
    154             //则在第1~3秒间是显示“look”,在第3~6秒间是显示"Up",在第6秒到下一个时间点显示"Down"
    155             if(offset >= nextTimeMill) {
    156                 lrcTextView.setText(msg);
    157                 msg = msgs.poll();
    158                 nextTimeMill = times.poll();
    159             }
    160             currentTimeMill = currentTimeMill + 100;
    161             //在run方法里调用postDelayed,则会形成循环,每0.01秒执行一次线程
    162             handler.postDelayed(updateTimeCallback, 100);
    163         }
    164     }
    165 }

    (2)PlayService.java

     1 package tony.mp3player.service;
     2 
     3 import java.io.File;
     4 
     5 import tony.model.Mp3Info;
     6 import tony.mp3player.AppConstant;
     7 import android.app.Service;
     8 import android.content.Intent;
     9 import android.media.MediaPlayer;
    10 import android.net.Uri;
    11 import android.os.Environment;
    12 import android.os.IBinder;
    13 
    14 public class PlayerService extends Service {
    15 
    16     private boolean isPlaying = false;
    17     private boolean isPause = false;
    18     private boolean isReleased = false;
    19     private MediaPlayer mediaPlayer = null;
    20     
    21     @Override
    22     public IBinder onBind(Intent intent) {
    23         return null;
    24     }
    25 
    26     @Override
    27     public int onStartCommand(Intent intent, int flags, int startId) {
    28         Mp3Info info = (Mp3Info) intent.getSerializableExtra("mp3Info");
    29         int MSG = intent.getIntExtra("MSG", 0);
    30         if(info != null) {
    31             if(MSG == AppConstant.PlayerMsg.PLAY_MSG) {
    32                 play(info);
    33             }
    34         } else {
    35             if(MSG == AppConstant.PlayerMsg.PAUSE_MSG) {
    36                 pause();
    37             } 
    38             else if(MSG == AppConstant.PlayerMsg.STOP_MSG) {
    39                 stop();
    40             }
    41         }
    42         return super.onStartCommand(intent, flags, startId);
    43     }
    44 
    45     private void stop() {
    46         if(mediaPlayer != null) {
    47             if(isPlaying) {
    48                 if(!isReleased) {
    49                     mediaPlayer.stop();
    50                     mediaPlayer.release();
    51                     isReleased = true;
    52                     isPlaying = false;
    53                 }
    54             }
    55         }
    56     }
    57 
    58     private void pause() {
    59         if(mediaPlayer != null) {
    60             if(!isReleased){
    61                 if(!isPause) {
    62                     mediaPlayer.pause();
    63                     isPause = true;
    64                 } else {
    65                     mediaPlayer.start();
    66                     isPause = false;
    67                 }
    68             }
    69         }
    70     }
    71 
    72     private void play(Mp3Info info) {
    73         if(!isPlaying) {
    74             String path = getMp3Path(info);
    75             mediaPlayer = MediaPlayer.create(this, Uri.parse("file://" + path));
    76             mediaPlayer.setLooping(false);
    77             mediaPlayer.start();
    78             isPlaying = true;
    79             isReleased = false;
    80         }
    81     }
    82 
    83     private String getMp3Path(Mp3Info mp3Info) {
    84         String SDCardRoot = Environment.getExternalStorageDirectory()
    85                 .getAbsolutePath();
    86         String path = SDCardRoot + File.separator + "mp3" + File.separator
    87                 + mp3Info.getMp3Name();
    88         return path;
    89     }
    90 }

    3.LrcProcessor.java

     1 package tony.mp3player;
     2 
     3 import java.io.BufferedReader;
     4 import java.io.InputStream;
     5 import java.io.InputStreamReader;
     6 import java.util.ArrayList;
     7 import java.util.LinkedList;
     8 import java.util.Queue;
     9 import java.util.regex.Matcher;
    10 import java.util.regex.Pattern;
    11 
    12 public class LrcProcessor {
    13 
    14     public ArrayList<Queue> process(InputStream inputStream) {
    15         Queue<Long> timeMills = new LinkedList<Long>();
    16         Queue<String> messages = new LinkedList<String>();
    17         ArrayList<Queue> queues = new ArrayList<Queue>();
    18         try {
    19             InputStreamReader inputReader = new InputStreamReader(inputStream);
    20             BufferedReader bufferReader = new BufferedReader(inputReader);
    21             //创建一个正则表达式对象,寻找两边都带中括号的文本
    22             Pattern p = Pattern.compile("\[([^\]]+)\]");
    23             String temp = null;
    24             String result = null;
    25             while((temp = bufferReader.readLine()) != null) {
    26                 Matcher m = p.matcher(temp);
    27                 if(m.find()) {
    28                     if(result != null) {//正则第一次到时是,此时result还没值,到下一次循环时,就会把第一次计算出的result加到队列里
    29                         messages.add(result);
    30                     }
    31                     String timeStr = m.group();
    32                     Long timeMill = time2Long(timeStr.substring(1, timeStr.length() - 1));
    33                     timeMills.offer(timeMill);//和add相比,offer不会抛异常
    34                     //取出时间串后面的歌词,如[00:02.31]Lose Yourself,得到“Lose Yourself”
    35                     String msg = temp.substring(timeStr.length());
    36                     result = "" + msg + "
    ";//防止msg为null时抛nullpoint
    37                 } else {
    38                     result = result + temp + "
    ";
    39                     //比如歌词如下:则上面的if会得到result = a + "
    ",
    40                     //而else里会使result = a + "
    " + b + "
    " + c + "
    "
    41                     //[00:32.42]a
    42                     //b
    43                     //c
    44                 }
    45             }
    46             messages.add(result);//把最后一次循环的result加到quenue里
    47             queues.add(timeMills);//把时间队列和歌词队列都加到list里
    48             queues.add(messages);
    49         } catch (Exception e) {
    50             e.printStackTrace();
    51         }
    52         return queues;
    53     }
    54 
    55     private Long time2Long(String timeStr) {
    56         //eg : 00:02.31
    57         String [] s = timeStr.split(":");
    58         int min = Integer.parseInt(s[0]);
    59         String ss[] = s[1].split("\.");
    60         int sec = Integer.parseInt(ss[0]);
    61         int mill = Integer.parseInt(ss[1]);
    62         return min * 60 * 1000 + sec * 1000 + mill * 10L;
    63     }
    64 
    65 }

    4.AppConstant.java

     1 package tony.mp3player;
     2 
     3 public interface AppConstant {
     4 
     5     public class PlayerMsg {
     6         public static final int PLAY_MSG = 1;
     7         public static final int PAUSE_MSG = 2;
     8         public static final int STOP_MSG =3;
     9     }
    10     public class URL {
    11         public static final String BASE_URL = "http://192.168.1.104:8080/mp3/";
    12     }
    13 }

     

  • 相关阅读:
    git rebase命令
    java中HashSet对象内的元素的hashCode值不能变化
    Spring中WebApplicationInitializer的理解
    mysql判断表字段或索引是否存在,然后修改
    mysql存储过程
    判断地图上的点是否在圆形,多边形,区域内
    计算任意多边形的面积、中心、重心
    判断点是否在任意多边形内
    springMvc将对象json返回时自动忽略掉对象中的特定属性的注解方式
    String.format()详细用法
  • 原文地址:https://www.cnblogs.com/shamgod/p/5198327.html
Copyright © 2011-2022 走看看