Android-歌词同步功能代码展示

本文通过MainActivity、LrcProccessor及Lyric类展示了Android应用中实现歌词同步播放的代码细节,涵盖了歌词加载、时间匹配及显示等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MainActivity:

import android.media.MediaPlayer;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, MediaPlayer.OnCompletionListener {

    private Button btnMediaPlayer;
    private MediaPlayer mp;
    private TextView tvShowLrc;
    private long begin; //获取开始播放media时的系统时间
    private long currentTimeMill; //歌词播放计时器
    private long nextTimeMill; //歌词时间戳
    private String lyric; //临时保存需要显示的歌词
    private Handler handler;
    private UpdateTimeCallback updateTimeCallback = null;
    private Queue time = null; //时间戳队列
    private Queue content = null; //歌词内容队列
    private final String textStart = "Start"; //播放/停止按钮文字内容
    private final String textStop = "Stop"; //播放/停止按钮文字内容

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

        //初始化View
        initViews();
    }

    /**
    * 初始化View
    */
    public void initViews() {
        //加载View控件
        btnMediaPlayer = (Button) findViewById(R.id.btnMediaPlayer);
        tvShowLrc = (TextView) findViewById(R.id.tvShowLrc);

        //设置按钮的文字内容为"Start"
        btnMediaPlayer.setText(textStart);

        //为按钮设置监听器
        btnMediaPlayer.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnMediaPlayer:
                //判断播放media还是停止media
                playOrStop();
                break;
        }
    }

    /**
    * 检测当前media播放状态,如果正在播放中则停止,如果是停止状态则进行播放
    */
    private void playOrStop() {
        //判断按钮的文字内容,并进行播放/停止的处理
        if (textStart.equals(btnMediaPlayer.getText().toString())) {
            //准备media资源
            mediaPrep();
            if (!mp.isPlaying()) {
                //准备歌词
                lyricPrep();
                //播放media
                mediaPlay();
                //开始同步歌词
                lyricStart();
                //将按钮的文字改成Stop
                btnMediaPlayer.setText(textStop);
            }
        } else if (textStop.equals(btnMediaPlayer.getText().toString())) {
            if (mp != null) {
                if (mp.isPlaying()) {
                    //停止播放media
                    mediaStop();
                    //停止同步歌词
                    lyricStop();
                    //将按钮文字改成Start
                    btnMediaPlayer.setText(textStart);
                }
            }
        }
    }

    /**
    * 准备media资源
    */
    private void mediaPrep() {
        //加载音频文件
        mp = MediaPlayer.create(this, R.raw.save_me_from_myself);
        //设置播放完成监听器
        mp.setOnCompletionListener(this);
    }

    /**
    * 播放media
    */
    private void mediaPlay() {
        if (mp != null) {
            mp.start();
        }
    }

    /**
    * 停止media
    */
    private void mediaStop() {
        if (mp != null) {
            mp.stop();
        }
    }

    /**
    * 准备歌词
    */
    private void lyricPrep() {
        //创建时间戳队列和歌词队列的对象实例
        time = new LinkedList<>();
        content = new LinkedList<>();

        try {
            //读取歌词并传入LyricProcessor的对象中进行处理
            InputStream in = getResources().getAssets().open("save_me_from_myself.lrc");
            LyricProcessor lp = new LyricProcessor(in);

            //将处理后的歌词保存至myLyric中
            ArrayList<Lyric> myLyric = lp.getLyric();

            //将myLyric中的时间戳和歌词分别保存至队列time,content中.
            for (int i = 0; i < myLyric.size(); i++) {
                Lyric l = myLyric.get(i);
                time.offer(l.getTime());
                content.offer(l.getContent());
            }

            //关闭流
            in.close();

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

    }

    /**
    * 开始同步歌词
    */
    private void lyricStart() {
        //实例化handler
        handler = new Handler();
        //初始化歌词同步的时间
        currentTimeMill = 0;
        //获取开始播放media时的系统时间
        begin = System.currentTimeMillis();
        //实例化updateTimeCallback,并传入时间戳队列和歌词队列
        updateTimeCallback = new UpdateTimeCallback(time, content);
        //开启线程
        handler.postDelayed(updateTimeCallback, 5);
    }

    /**
    * 停止歌词
    */
    private void lyricStop() {
        if (updateTimeCallback != null) {
            handler.removeCallbacks(updateTimeCallback);
            tvShowLrc.setText("");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mp != null) {
            if (mp.isPlaying()) {
                mp.stop();
                mp.release();
            } else {
                mp.release();
            }
        }
        lyricStop();
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mediaStop();
        lyricStop();
        btnMediaPlayer.setText(textStart);
    }

    class UpdateTimeCallback implements Runnable {
        Queue time = null;
        Queue content = null;

        public UpdateTimeCallback(Queue time, Queue content) {
            this.time = time;
            this.content = content;
        }

        @Override
        public void run() {
            if (time.peek() != null && content.peek() != null) {
                //用系统当前时间减去开始播放media的时间,获得时间差offset
                long offset = System.currentTimeMillis() - begin;

                //判断是否第一行歌词
                if (currentTimeMill == 0) {
                    nextTimeMill = (Long) time.poll();
                    lyric = (String) content.poll();
                }
                //判断当offset>= 第一个时间戳时,显示歌词,并将time,content两个队列中的下一个值取出
                if (offset >= nextTimeMill) {
                    tvShowLrc.setText(lyric);
                    lyric = (String) content.poll();
                    nextTimeMill = (Long) time.poll();
                }

                //增加当前的歌词计时器以10毫秒不断增长
                currentTimeMill = currentTimeMill + 10;
                //延时10毫秒重复执行此任务
                handler.postDelayed(updateTimeCallback, 10);
            }
        }
    }
}

LrcProccessor:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LyricProcessor {

    private InputStream inputStream; //歌词文件输入流

    public LyricProcessor(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public ArrayList<Lyric> getLyric() {
        //将分析获取到的歌词保存至lyric里面
        ArrayList<Lyric> lyric = new ArrayList<>();

        //创建流实例
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        //通过分析歌词文件,分离时间段和歌词内容
        String line;//临时存放歌词文件每一行的数据
        Long time; //每一个时间点
        String content;//每一句歌词
        Pattern p = Pattern.compile("\\[([^\\]]+)\\]");//正则表达式
        try {
            while ((line = bufferedReader.readLine()) != null) {
                Matcher m = p.matcher(line);//每一行歌词匹配规则
                if (m.find()) {
                    String timeStr = m.group();//将时间戳临时保存
                    time = time2Long(timeStr.substring(1, timeStr.length() - 1));//转换时间戳为毫秒数
                    content = line.substring(10);//读取歌词内容
                    Lyric eachLyric = new Lyric(time, content);//将时间戳对应的歌词内容放入Lyric的对象中
                    lyric.add(eachLyric); //保存每一行歌词至ArrayList中
                } else {
                    System.out.println("没有找到歌词");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return lyric;
    }

    /**
    * 将歌词里的时间转换成毫秒
    *
    * @param timeStr 歌词文件中的时间戳
    * @return
    */
    private Long time2Long(String timeStr) {
        //通过":"符号将分钟分离为min
        String s[] = timeStr.split(":");
        int min = Integer.parseInt(s[0]);

        //通过"."符号分离秒与分秒
        String ss[] = s[1].split("\\.");
        int sec = Integer.parseInt(ss[0]);
        int mill = Integer.parseInt(ss[1]);

        return min * 60000 + sec * 1000 + mill * 10L;//返回时间戳的毫秒数
    }
}

Lyric:

public class Lyric {

    private long time;//时间戳
    private String content;//歌词内容

    public Lyric(long time, String content) {
        this.time = time;
        this.content = content;
    }

    public long getTime() {
        return time;
    }

    public String getContent() {
        return content;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

githan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值