• 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 }

     

  • 相关阅读:
    windows下Yarn安装与使用(两种方法)
    git配置公钥---解决码云出现git@gitee.com: Permission denied (publickey)
    npm使用国内镜像的两种方法
    【LeetCode】33. Search in Rotated Sorted Array (4 solutions)
    【LeetCode】83. Remove Duplicates from Sorted List
    【LeetCode】82. Remove Duplicates from Sorted List II
    【LeetCode】85. Maximal Rectangle
    【LeetCode】84. Largest Rectangle in Histogram
    【LeetCode】87. Scramble String
    【LeetCode】162. Find Peak Element (3 solutions)
  • 原文地址:https://www.cnblogs.com/shamgod/p/5198327.html
Copyright © 2020-2023  润新知