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来解决此问题

二、简介

ANDROID_MARS学习笔记_S01原始版_023_MP3PLAYER004_同步显示歌词_第1张图片

 

ANDROID_MARS学习笔记_S01原始版_023_MP3PLAYER004_同步显示歌词_第2张图片

三、代码
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 + "\n";//防止msg为null时抛nullpoint
37                 } else {
38                     result = result + temp + "\n";
39                     //比如歌词如下:则上面的if会得到result = a + "\n",
40                     //而else里会使result = a + "\n" + b + "\n" + c + "\n"
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 }

 

 

 

你可能感兴趣的:(ANDROID_MARS学习笔记_S01原始版_023_MP3PLAYER004_同步显示歌词)