• 首先我是爬虫获取的酷我的音源,因此歌词也是通过爬虫获取的,下面这个方法可以获取到歌曲对应的歌词信息。简单说下,在搜索歌曲之后会返回一个歌曲列表,查看源代码是包含在li标签里面的,这个li标签里面就有请求播放页面的地址,拿到这个地址请求单个歌曲页面播放的时候响应的html里面的js就包含了歌词信息,然后大致是var lrcList = [{"time": "1.8","lineLyric": "九张机-(网剧《双世宠妃》主题曲) - 叶炫清 "}这种格式的,我将每一时间段歌词转换为KuwoLyric对象:
public List<KuwoLyric> getLyric(KuwoLiLabel label) {
		String url = null;

		if (StringUtil.isEmpty(label.getNewPlayUrl())) {
			if (StringUtil.isEmpty(label.getOldPlayUrl())) {
				throw new MarsException("获取不到播放页面的url地址!");
			}
			url = label.getOldPlayUrl();
		} else {
			url = label.getNewPlayUrl();
		}

		HttpGet get = new HttpGet(url);
		CloseableHttpClient client = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		try {
			response = client.execute(get);
			String lrcHtml = EntityUtils.toString(response.getEntity(), "utf-8");
			Elements elements = Jsoup.parse(lrcHtml).select("head script");
			Pattern pattern = Pattern.compile("\\[.*?\\]");
			Matcher matcher = pattern.matcher(elements.html());
			if (matcher.find()) {
				String jsonLrc = matcher.group();
				return JSON.parseArray(jsonLrc, KuwoLyric.class);
			}

		} catch (Exception e) {
			System.out.println("获取歌词信息出错  对应的歌曲是:"+label.getNewMusicName());
			e.printStackTrace();
		}

		return null;
	}


@AllArgsConstructor
@Getter
@ToString
public class KuwoLyric {

	private String time;
	private String lineLyric;

}
  • 获取到了歌词信息就是展示了,肯定是需要另外开一个线程来同步显示歌词的,歌词同步采用while循环的方式:

     期间遇到问题如下

         1.歌曲停止再次播放的时候就会再开一个线程,这样下去线程就会越来越多。解决方式是:(1)可以定义一个volatile 修饰的线程终止变量,当停止再播放的时候需要先结束之前的歌词同步线程再启动新线程,结束线程就改变一下停止变量就行了(有个缺点就是时效性不高,当歌词线程还在睡眠中时是不会调到if里面去终止线程的,这时候切歌前面的歌词同步线程并没有关闭)。(2)采用中断线程的操作,这样线程会立即中断,在捕获到线程被中断之后立马结束当前线程。

         2.运行程序,切歌时内存和cpu占用率会成倍增加。经排查成倍增加是因为每次切歌我是直接调用显示歌词线程,因此每次切都会成倍增加,解决办法仍然是第一个问题中方法,在每次切歌之前需要先终止之前歌词线程。

         3.播放歌曲cpu占用率稳定,但是很高,经排查,由于我是通过while(true)一直比较当前播放时间和歌词时间,来显示歌词的,所以cpu一直占用高,解决办法是,先取出第一段歌词,计算和当前播放时间差值,之后再使歌词线程睡这个差值时间,之后再次进行判断,这样while就不会持续占用cpu资源。

 

显示歌词代码如下(采用中断操作):TextField是显示歌词的文本框,KuwoPojo是音乐实体类。先得到歌词集合再遍历每个小段歌词对象,拿出时间和播放器的当前时间比较,当播放时间接近歌词时间时候就显示当前对应的歌词。

public class LyricShowUtil {

	// 必须设置为volatile才能改变线程状态
	public volatile boolean isStop = false;
    public Thread lyricThread;
	// enum PlayStatus {}

	public void readyLyric(TextField lrcText, KuwoPojo nowMusic, MediaPlayer player) {
		// 得到每段歌词组成的列表
		List<KuwoLyric> lyric = KuwoMusic.obj.getLyric(nowMusic.getLabel());
		System.out.println("歌词列表:" + lyric);
		lrcText.setText("**********wait**********");
		for (KuwoLyric kuwoLyric : lyric) {
			String time = kuwoLyric.getTime();

			while (true) {
				if (this.isStop) {
					System.out.println("**********结束歌词显示线程**********");
					return;
				} ;

				Double temp=Double.valueOf(time).doubleValue()*1000- player.getCurrentTime().toMillis();//单位是毫秒ms
				// 设置歌词显示精度
				if ( temp< 0.1) {
					System.out.println("======显示歌词" + Double.valueOf(time).doubleValue() + " "
							+ player.getCurrentTime().toSeconds());
					System.out
							.println(lrcText + "  " + kuwoLyric + "  " + kuwoLyric.getLineLyric());
					lrcText.setText(kuwoLyric.getLineLyric());
					break;
				}else {
					try {
						TimeUnit.MILLISECONDS.sleep(temp.longValue());
					} catch (InterruptedException e) {
						System.out.println("歌词显示线程readyLyric出错");
						e.printStackTrace();
					}
				}
			}
		}

	}

	public void showLyricInfo(TextField lrcText, KuwoPojo nowMusic, MediaPlayer player) {

		// 之前可能中断过线程,因此每次调用需要重新设值
		this.isStop = false;
		lyricThread = new Thread(() -> {
            try {
			    readyLyric(lrcText, nowMusic, player);
            } catch (InterruptedException e) {
				System.out.println("歌词显示线程被中断  " + e.getMessage());
				return;
			}
		}, "歌词展示线程");
        lyricThread.start();
	}
}

点击播放按钮触发的方法如下:该方法在Controller里面,selectMusic是当前选中的音乐,nowMusic是当前播放的音乐,player是MediaPlayer对象。

public void play(ActionEvent event) {
		
		if (player!=null&&selectMusic == nowMusic) {// 暂停/停止  再播放同一首歌的时候
			if (player.getStatus().toString().equals("STOPPED")) {//停止再播放  同一首歌
	            lyricShowUtil.lyricThread.interrupt();// 停止之前歌词同步线程
				lyricShowUtil.showLyricInfo(lrcText, nowMusic, player);
			}else {//暂停-->播放
				System.out.println("暂停---》播放");
			}
		} 
		
		if (player == null||selectMusic!=nowMusic) {// 第一次播放的时候  or 切歌,播放当前选中的歌曲
			if (player==null) {
				lyricShowUtil = new LyricShowUtil();
			}else {
				lyricShowUtil.lyricThread.interrupt();// 停止之前歌词同步线程
				player.dispose();
			}
			// 获取当前选中的label
			nowMusic = musicList.getSelectionModel().getSelectedItem();
			Media media = new Media(nowMusic.getMp3PlayUrl());
			player = new MediaPlayer(media);
			System.out.println("====正在播放=======" + nowMusic);
			
			lyricShowUtil.showLyricInfo(lrcText, nowMusic, player);
		}

完整音乐播放器代码请见github:https:///MrLawrenc/MarsTools  在music包里面有完整的音乐播放器代码