模拟网易云的H5音乐播放器

转发我在github发布的一个H5音乐播放器
H5MusicPlayer GitHub链接 欢迎Fork & Star。

前言

  • 这是我第一个GitHub项目,之前一直想在GitHub写点东西,近期又在学前端,刚好学到audio标签,平常时也比较喜欢听音乐写代码,因此就萌生了自己写一个音乐播放器的想法。利用了下班和周末的空闲时间,用了两周时间终于写出来了。当用代码一个个地把自己的想法实现出来,那种兴奋和成就感是无与伦比的。甚至周末的时候一码就码到通宵,还不觉得累哈哈。因为我也是初学,用GitHub把自己的注释和思路写出来,可以提供给跟我一样的伙伴来练手。
  • 首先先要感谢两位大神的知识分享:
    • 王乐平:CSDN博客
    • CeuiLiSA:GitHub首页
      该音乐播放器是基于王乐平的基础上修改的,借鉴了他的思路进行完善和添加功能。该博客的链接为一步一步实战HTML音乐播放器。实现的思路写得非常清晰,让我对写播放器摸着了门路。而CeuiLiSA则详细地分享了网易云最新的接口网易云音乐API,可以利用这些接口获得歌单、歌曲、歌词、专辑图片等,让播放器的功能更加完善。

音乐播放器效果

  • 我把音乐播放器放到了腾讯云服务器了,可以直接点击下面链接查看效果,音乐播放器具备的功能基本都实现了。如当前歌单音乐列表、输入网易云用户名获取歌单进行切换、音乐列表歌名搜索、网络音乐搜索、电脑本地音乐添加播放、歌词滚动显示、歌曲播放进度等。

  • H5MusicPlayer在线播放

  • 音乐播放器的整个界面效果:
    模拟网易云的H5音乐播放器_第1张图片

  • 从上到下的布局分为“音乐列表”“您的歌单”button按钮、专辑图片、歌名、歌手、歌词、播放控制按钮、歌曲时长、当前时间、播放进度条。

页面代码布局

音乐播放器
  • 歌词

歌词

  • 除了主页面显示的元素,还隐藏着下面的元素:
  • 这些元素会在按钮点击的情况触发显示,元素的隐藏和显示主要利用display的block或者none来控制。音乐列表的内容主要用iframe标签内嵌页面来显示。

播放器实现的逻辑思路

  • 首先音乐播放器有三个重要的对象:
    1、当前播放音乐:currentPlaySong
    2、当前播放音乐列表:musicInfos
    3、当前播放状态:playStatus
  • currentPlaySong的属性为:
	{	
		songListName: "";  //所属的歌单名称 
		songID:"";  //歌曲ID,网易云音乐每首歌都有固定的ID,通过ID可以获得歌曲的播放链接
		songName: "";//歌名
		artist: "";//歌手
		albumPic: "";//专辑图片url
		totalTime: "";//歌曲总时长
		mp3Url: "";//该url的格式是http://music.163.com/song/media/outer/url?id=3986241
        mp3Url2: "";//该url的格式是"https://m7.music.126.net/20181016104636/965ff036084dc30ec291460d1f4f85e3/ymusic/8827/447f/9ef4/3f399b1fd6d919555b55691e6632366d.mp3"
        上面两个是不同的链接格式,第一个链接我公司的网络设置了禁止访问网易云音乐163网站,所以无法播放,但一般的网络都可以访问。
        第二个链接则是通过网易云api接口获取到的,公司的网络没有禁止,可以访问,当然一般的网络也可以访问。
        两个链接都同时添加到audio下的source下,一个链接失效了,另一个链接可以使用。
		connectTimes: ""; //记录从网易云api接口获取了多少次mp3Url链接,超过一定的次数则停止获取,避免ID失效,死循环获取
		index: "" //是在歌曲列表中的第几首歌
	};
  • musicInfos就是一个数组,存储列表中的所有音乐对象。
  • playStatus的属性为:
{
	currentTrackLen: 0; //当前播放列表总的歌曲数 
	currentTrackIndex: 0;  //当前播放的是列表中的第几首歌
	currentTime: 0;  //当前播放的时长
	currentTotalTime: 0;  //当前播放歌曲的总时长
	playTimes: 0;  //点击上一首下一首的总次数,当达到10次就更新网易云接口获取的mp3Url链接,因为这个链接网易云设置了失效,30分钟左右就会失效
	_playStatus: false;  //使用下划线前缀表示这是受保护的对象,即protected
};
  • 主要的流程为:
    模拟网易云的H5音乐播放器_第2张图片

异步获取歌单列表的代码为:

//获取歌单的所有歌曲信息
function initSongs(url) {
	var connectUrl = "";
	if(url) {
		connectUrl = url;
	} else {
		connectUrl = playlistUrl + playListID;
	}
	//通过ajax同步请求数据,如果网络异常则给出提示
	$.ajax({
		url: connectUrl,
		type: "post",
		dataType: 'JSON',
		async: false,
		cache: true,
		success: getSongId,
		error: function(xhr, status, error) {
			//alert("抱歉,服务器故障了。\n你可以播放默认歌曲和本地音乐。");
			//如果网络问题或者服务器挂了,则加载预先下载好的歌单信息
			getSongId(playListTest);
		}
	});
}

//根据网易云接口返回的信息初始化歌曲列表
function getSongId(data) {
	var tracks = data.playlist.tracks;
	//刷新歌曲之前,先把之前的歌曲置空
	musicInfos = [];
	for(var i = 0; i < tracks.length; i++) {
		var musicInfo = new Object();
		//只在第一个对象中存储歌单名称
		if(i == 0) {
			musicInfo.songListName = data.playlist.name;
		}
		musicInfo.songID = tracks[i].id;
		musicInfo.songName = tracks[i].name;
		//为了简洁,只获取第一个歌手的名
		musicInfo.artist = tracks[i].ar[0].name;
		musicInfo.albumPic = tracks[i].al.picUrl + '?param=270y270';
		musicInfo.totalTime = tracks[i].dt;
		musicInfo.mp3Url = "http://music.163.com/song/media/outer/url?id=" + tracks[i].id + ".mp3";
		musicInfo.mp3Url2 = ""; //第二个链接先置空,后面利用空闲时间异步获取,保证应用性能
		//connetTimes记录当前歌曲从接口获取mp3Url连接的次数,
		//超过5次则停止获取,
		//避免歌曲ID失效,网易云接口传过来的是空url,造成浪费资源多次获取
		musicInfo.connectTimes = 0;
		musicInfo.index = i;
		musicInfos.push(musicInfo);
	}

	//初始完成之后主要把赋值给正在播放的列表
	currentPlayList = musicInfos;
	currentPlayListIndex = 0;

	//现在有了新的解决mp3Url的方法,就是获取到歌曲ID后直接
	//在该链接后面加上ID的值http://music.163.com/song/media/outer/url?id= + id.mp3,
	//这为获取url链接提供了极大的方便,
	//既不用担心url链接失效,也不用担心获取url的接口失效或者IP被禁,
	//也避免了多次耗费资源循环地更新url链接

	//先同步获取第一首歌曲的第二个mp3Url2链接,
	//后面才异步获取当前播放歌曲的前10首和后10首。
	//由于网易云接口获取的url有时间限制,超过大概半个小时后url链接就会失效,
	//所以用户点击上一首或下一首共10次之后或者半个小时之后就会更新链接
	getMp3Url(musicInfos, 0, 1, 0, false);
}

//var errorCount = 0;
//initList当前需要获取url链接的数组,为musicInfos或者songSearchResults
function getMp3Url(initList, startindex, endIndex, goalIndex, isAsync, isCached) {

	for(var i = startindex; i < endIndex; i++) {
		var songUrl = "";
		if(goalIndex) {
			songUrl = "https://api.imjad.cn/cloudmusic/?type=song&id=" + initList[goalIndex].songID + "&br=128000";
		} else {
			songUrl = "https://api.imjad.cn/cloudmusic/?type=song&id=" + initList[i].songID + "&br=128000";
		}
		$.ajax({
			url: songUrl,
			type: "get",
			dataType: 'JSON',
			crossDomain: true,
			async: isAsync,
			cache: isCached ? isCached : isAsync,
			success: function(data) {
				if(isAsync) {
					var times = 0;
					while(times < initList.length) {
						//为防止异步加载数据顺序错乱,只有songID对应时才添加mp3Url链接
						if(initList[startindex].songID != data.data[0].id) {
							startindex = (startindex + 1) % initList.length;
							times++;
						} else {
							initList[startindex].mp3Url2 = data.data[0].url;
							startindex = (startindex + 1) % initList.length;
							break;
						}
					}
				} else {
					initList[goalIndex].mp3Url2 = data.data[0].url;
				}
			},
			error: function(xhr, status, error) {
				console.log(xhr.status);
				//errorCount++;
			}
		});
	}
}

初始化播放状态的代码为:

//当前播放器状态
var playStatus = {
	currentTrackLen: 0,
	currentTrackIndex: 0,
	currentTime: 0,
	currentTotalTime: 0,
	playTimes: 0, //点击上一首下一首的次数
	//使用下划线前缀表示这是受保护的对象,即protected
	_playStatus: false,
};

function initPlayStatus() {
	if(currentPlayList) {
		playStatus.currentTrackLen = currentPlayList.length;
	} else {
		playStatus.currentTrackLen = 0;
	}
	playStatus.currentTrackIndex = 0;
	playStatus.currentTime = 0;
	//因为timgTask会不断读取音乐的currentTime,
	//当点击本地文件夹音乐列表调用initPlayStatus()时,timgTask还会继续读取,
	//导致currentTime > currentTotalTime,误以为当前歌曲已经播放完成,
	//从而不断地播放一下首,导致bug
	playStatus.currentTotalTime = 10000000;
	playStatus.playTimes = 0;
	playStatus._playStatus = false;
}

初始化前端界面代码为:

//播放器控制方法
	playerControls = {
		//歌曲基本信息
		trackInfo: function(args) {
			//保存现在正在播放的歌曲
			currentPlaySong = args;

			//先添加audio元素
			$('#audio').remove();
			$('.player').append('');
			$('#source1').attr('src', args.mp3Url2);
			$('#source2').attr('src', args.mp3Url);

			//加载audio音频
			$("#audio")[0].load();

			if(args.isLocal) {
				//如果是本地文件夹的歌曲,则在audio音频元素加载完成时进行读取音频时长
				$("#audio")[0].onloadedmetadata = function() {
					//音频时长单位为秒
					var totalTime = $("#audio")[0].duration;
					$('.player .time .total').text(timeConvert(totalTime));
					playStatus.currentTotalTime = totalTime - 1;
					args.totalTime = totalTime;
				}
			} else {
				//网易云接口返回的音频时长单位为毫秒
				//显示这首歌的时长,
				$('.player .time .total').text(timeConvert(args.totalTime / 1000));
				//减1是为了避免计算误差,无法自动下一首
				playStatus.currentTotalTime = Math.floor(args.totalTime / 1000) - 1;
			}

			//根据歌名长度设置字体大小,避免歌名太长超出边框
			$('.player .trackInfo .name p').text(args.songName);
			if(args.songName.length >= 40) {
				$('.player .trackInfo .name').css("font-size", "18px");
			} else if(args.songName.length > 30) {
				$('.player .trackInfo .name').css("font-size", "22px");
			} else {
				$('.player .trackInfo .name').css("font-size", "26px");
			}
			//歌手名称
			$('.player .trackInfo .artist').text(args.artist);
			//歌曲图片
			if(args.isLocal) {
				$('.player .albumPic').css('background', 'url(img/artist.jpg)');
			} else {
				$('.player .albumPic').css('background', 'url(' + args.albumPic + ')');
			}

			//获取歌词
			getLyric();
		}
	}	

播放音乐代码为:

(主要是做了很多网络加载资源出错或mp3链接失效的处理)

//播放、暂停状态处理
playStatus: function() {
$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));

if(playStatus.playStatus) {
	//networkState = 3则说明音乐mp3URl链接失效,找不到资源,需要重新获取歌曲url链接
	//networkState = 0则说明该音乐的mp3Url为空
	if($("#audio")[0].networkState != 3 && $("#audio")[0].networkState != 0) {
		
		$("#audio")[0].play();

		//如果5秒后还是没有任何资源(reayState=0)而且网络仍然在加载(networkState=2)
		//则重新加载刷新歌曲,重新加载次数不超过3次,仍然失败则是网络问题
		setTimeout(function() {
			console.log("readyState:" + $("#audio")[0].readyState);
			console.log("networkState:" + $("#audio")[0].networkState);
			console.log("error:" + $("#audio")[0].error);
			if($("#audio")[0].readyState == 0 &&
				$("#audio")[0].networkState == 2) {
				//重新刷新的歌曲信息
				playerControls.trackInfo(currentPlaySong);
				$("#audio")[0].load();
				loadTimes++;
				//playStatus.playStatus = false;
				//先停顿0.1秒,让程序先加载音乐资源,否则networkState属性会为3,意为找不到资源
				if(loadTimes <= 4) {
					setTimeout(function() {
						playerControls.playStatus();
					}, 300);
				} else {
					loadTimes = 0;
					playStatus.playStatus = false;
					alert("加载歌曲失败,请检查网络。");
				}
			} else if($("#audio")[0].networkState == 3) {
				//如果5秒后该音乐的网络状态为3,则说明请求资源失效
				loadTimes = 0;
				invalidTimes++; //歌曲失效次数增加,超过3次刷新url链接
				playerControls.trackInfo(currentPlaySong);
				$("#audio")[0].load();
				//调用播放方法去刷新链接
				playerControls.playStatus();
			}
		}, 5000);

		//当点击下一首上一首的次数超过8次,对url进行更新
		//因为指定了ajax使用cached,已经加载url的不用重新连接网易云接口
		if(playStatus.playTimes >= 8 && currentPlayListIndex != 2) {
			init20Songs();
			playStatus.playTimes = 0;
		}

		//显示第一句歌词
		if(lyricResult.length <= 0) {
			$("#lyric").text("纯音乐,请欣赏。");
		} else {
			$("#lyric").text(lyricResult[0][1]);
		}

	} else {
		//先判断是否已经获取了3次
		if(currentPlaySong.connectTimes < 3) {
			//同步获取当前失效歌曲的url链接
			if(currentPlayListIndex == 1) {
				//如果是网络搜索结果则更新songSearchResults
				getMp3Url(songSearchResults, 0, 1, playStatus.currentTrackIndex, false);
				songSearchResults[currentPlaySong.index].connectTimes++;
				playStatus.playStatus = false;
				//重新加载失效的歌曲信息
				playerControls.trackInfo(songSearchResults[currentPlaySong.index]);
			} else if(currentPlayListIndex == 0) {
				//否则更新musicInfos
				getMp3Url(musicInfos, 0, 1, playStatus.currentTrackIndex, false);
				musicInfos[playStatus.currentTrackIndex].connectTimes++;
				playStatus.playStatus = false;
				//重新加载失效的歌曲信息
				playerControls.trackInfo(musicInfos[playStatus.currentTrackIndex]);
			}
			$("#audio")[0].load();
			//先停顿0.1秒,让程序先加载音乐资源,否则networkState属性会为3,意为找不到资源
			setTimeout(function() {
				playerControls.playStatus();
			}, 300);
			alert("歌曲链接失效,请重新点击播放键。");
		} else {
			playStatus.playStatus = false;
			alert("抱歉,该歌曲获取失败,请换下一首歌。");
		}

					//失效次数超过3次则说明缓存的url链接几乎都失效了,需要重新获取,刷新缓存
					invalidTimes++;
					if(invalidTimes >= 3 && currentPlayListIndex != 2) {
						init20Songs(false, false);
						invalidTimes = 0;
					}
				}
			} else {
				if($("#audio")[0].played) {
					$('#audio')[0].pause();
					//恢复歌词字样
					if(lyricResult.length <= 0){
						$("#lyric").text("纯音乐,请欣赏。");
					}else{
						$("#lyric").text("歌词");
					}
					
				}
			}
		}

播放进度条和加载动画的代码为:

var timeOut;
//启动定时任务,加载时间进度条和显示加载动画
function timingTask() {
	//因为interval会有累积效应,比如alert一个窗口,用户很久都还没点击,
	//这时线程因为alert而堵塞,但interval仍然会计算时间,
	//将到时间但还没执行的操作添加到队列中。
	//当用户点击后,累积的interval就会一次性按顺序执行,
	//此时一个clearInterval便无法把所有的interval停止了。
	//因此会造成性能问题。
	//因此时间间隔比较小的尽量使用内嵌setTimeOut来代替。
	//注意:setTimeOut也会累积
	playStatus.currentTime = $('#audio')[0].currentTime;
	playerControls.playTime();

	if(playStatus.currentTime >= playStatus.currentTotalTime) {
		$('.player .controls .next').click();
	}

	//根据网络状态显示加载动画
	//readyState == 0 表示have-nothing,还没开始加载资源
	//readyState == 2 表示已经有当前数据,但是还没有下面播放的数据
	//readyState == 4 表示已经有足够的数据
	//networkState == 2 表示请求网络资源正在加载中
	//networkState == 1 表示已经请求到网络资源,可以播放了
	if(($("#audio")[0].readyState != 4) &&
		$("#audio")[0].networkState != 1) {
		if($(".loader:first").css("display") == "none") {
			$(".loader:first").css("display", "block");
		}
	} else {
		if($(".loader:first").css("display") == "block") {
			$(".loader:first").css("display", "none");
		}
	}

	//如果playStatus为true则循环调用自己,
	//否则将自己停止
	if(playStatus.playStatus) {
		timeOut = setTimeout(timingTask, 300);
	} else {
		clearTimeout(timeOut);
		//如果加载动画还在加载,则停止加载
		if($(".loader:first").css("display") == "block") {
			$(".loader:first").css("display", "none");
		}
		//刷新播放按钮状态
		$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));
	}
}

//使用definedProperty方法监听playStatus属性,当为true的时候才进行定时任务
Object.defineProperty(playStatus, 'playStatus', {
	get: function() {
		return this._playStatus;
	},
	set: function(value) {
		this._playStatus = value;
		if(value == true) {
			//开启定时任务
			timingTask();
		} else {
			//按了停止键,则停止定时任务
			clearTimeout(timeOut);
			//如果加载动画还在加载,则停止加载
			if($(".loader:first").css("display") == "block") {
				$(".loader:first").css("display", "none");
			}
			//刷新播放按钮状态
			$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));
		}
	}
});

播放电脑本地音乐的代码为:

function getLocalFiles() {
	//如果本地音乐列表不存在,则进行选择音乐文件
	if(localMusicFiles.length <= 0 || $("#localMusicList").css("display") == "block") {
		if($("#localMusic").length != 0) {
			$("#localMusic").fadeIn(200);
		} else {
			$("body").append('
' + '
请选择您的音乐文件:' + '
' + '
(可以一次性选多首音乐)' + '
' + '
' + '
' + '
'); } $(".icon-cross3").unbind("click").click(function() { $("#localMusic").fadeOut(200); }); $("#music_confirm").unbind("click").click(function() { var musicFiles = document.getElementById("musicFiles"); //value属性保存的是文件的绝对路径,如果为空则说明没有选到文件 if(musicFiles.value) { $("#localMusic").fadeOut(200); var files = musicFiles.files; var hasNotEmpty = true; var isMp3 = false; for(var i = 0; i < files.length; i++) { //如果选择的文件是音频文件才进行添加 if(/audio\/\w+/g.test(files[i].type)) { //存在音频文件才把上次选择的本地音乐清空,再进行添加 if(hasNotEmpty) { localMusicFiles = []; hasNotEmpty = false; isMp3 = true; } var musicInfo = new Object(); musicInfo.songID = -1; musicInfo.songName = files[i].name; musicInfo.artist = "本地歌曲"; musicInfo.albumPic = ""; //本地歌曲时长时间还需要获取数据处理 musicInfo.totalTime = 210000; musicInfo.mp3Url = ""; musicInfo.mp3Url2 = ""; musicInfo.connectTimes = 0; musicInfo.index = i; //先将本地音乐文件的files对象保存,等到用户点击歌曲的时候才进行加载播放 musicInfo.musicData = files[i]; //标志该歌曲是本地歌曲 musicInfo.isLocal = true; localMusicFiles.push(musicInfo); } } //如果选择的所有文件都不是音频文件则进行提醒 if(!isMp3) { alert("请选择音频文件"); $("#localMusic").fadeIn(0); } else { //将本地音乐数据传送到本地音乐列表页面 window.frames[4].postMessage(localMusicFiles, "/"); $("#musicList").css("display", "none"); $("#songList").css("display", "none"); $("#searchMusicList").css("display", "none"); $("#netSearchList").css("display", "none"); $("#localMusicList").css("display", "block"); } } else { alert("请选择音乐文件"); } }); } else { //如果本地音乐文件列表已经存在,则直接显示出来 listButtonShow(true); $(".musicList").css("display", "none"); $("#title").css("display", "none"); $("#search").css("display", "block"); $("#local").css("display", "block"); $("#localMusicList").css("display", "block"); } }

解析歌词的代码为:

//调用trackInfo方法加载音乐信息的时候,也进行加载歌词,并进行解析
function getLyric() {
	if(currentPlaySong.isLocal) {
		//如果是本地音乐,则把歌词隐藏
		$(".lyric").css("display", "none");
	} else {
		//如果是网络音乐,则把歌词显示
		$(".lyric").css("display", "block");
		//通过ajax异步获取歌词信息,如果网络异常则给出提示
		$.ajax({
			url: lyricApi + currentPlaySong.songID,
			type: "get",
			dataType: 'JSON',
			async: true,
			cache: true,
			success: processLyric,
			error: function(xhr, status, error) {
				alert("抱歉,获取歌词失败,服务器出故障了。");
			}
		});
	}
}

//解析歌词
function processLyric(lyricData) {
	//正则表达式,匹配[00:59.40]这是时间格式
	var pattern = /\[\d{2}:\d{2}.\d+\]/g;
	
	//如果标志没有歌词或者歌词内容不匹配时间格式,则归为纯音乐
	if(lyricData.nolyric == true || !pattern.test(lyricData.lrc.lyric)) {
		lyricResult = [];
		lyricArtistMessage = [];
		$("#lyricDiv").empty();
		$("#lyricDiv").append("
  • 纯音乐,请欣赏。
") $("#lyric").text("纯音乐,请欣赏。"); return; } var lyricInfo = lyricData.lrc.lyric; var lines = lyricInfo.split("\n"); //用来存储解析出来的歌词,格式如下[[time,lyricString],[time,lyricString],.....] lyricResult = []; lyricArtistMessage = []; //有一些歌词前面时歌手的介绍,没有歌词的,需要把它去掉 //直到匹配到有时间的歌词才会停止循环 var artistPattern = /\[\w+:/g; //需要注意,正则表达式使用g模式的话,下一次匹配会从lastIndex开始匹配, //因为上面使用了pattern进行了匹配,有可能lastIndex不为0,所以需要重置为0 pattern.lastIndex = 0; while(!pattern.test(lines[0])) { //先把歌手信息保存下来,因为全屏歌词的时候需要显示 var lyricMessage = lines[0].replace(artistPattern, "").slice(0, -1); lyricMessage.length > 0 ? lyricArtistMessage.push(lyricMessage) : 0; //去掉第一个元素并返回剩下元素 lines = lines.splice(1); } //lines最后一行是空的话把它去除掉 lines[lines.length - 1].length == 0 && lines.pop(); lines.forEach(function(value, index, array) { //返回匹配的时间 var time = value.match(pattern); //将时间置换成空格,返回歌词字符串内容 var lyricString = value.replace(pattern, ""); //由于一行歌词会有多个时间, 如[03:33.65][03:35.39],所以需要进一步分离 time.forEach(function(value2, index2, array2) { //去掉前后的[] var t = value2.slice(1, -1).split(":"); //用秒数表示当前歌词的时间 var seconds = parseInt(t[0]) * 60 + parseFloat(t[1]); //将时间和歌词以数组的形式压进lyriclyricResult中 lyricResult.push([seconds, lyricString]); }); }); //将结果按照时间排序,保证歌词正确有序输出 lyricResult.sort(function(a, b) { //如果想要a在b前面,则返回一个负数,否则a想排在b后面,则返回一个正数 //所以想要元素按照升序排序,则返回a与b的差值就行 return a[0] - b[0]; }); addLyric(); //把歌词位置置为初始化 lyricIndex = 1; showLyric(); } function addLyric() { $("#lyricDiv").empty(); var ul = $("
    "); //歌手信息 for(var i = 0; i < lyricArtistMessage.length; i++) { ul.append("
  • " + lyricArtistMessage[i] + "
  • ") } //歌词信息 for(var i = 0; i < lyricResult.length; i++) { ul.append("
  • " + lyricResult[i][1] + "
  • ") } $("#lyricDiv").append(ul); } //监听audio的timeUpdate事件, //第一句歌词会在点击播放按钮时显示出来, //如果当前时间已经大于第一句歌词的时间,则说明第一句唱完了, //则把第一句隐藏,更改歌词内容,显示第二句歌词. //再把当前时间跟第三句歌词时间进行比较,依次循环. var lyricIndex = 1; //记录当前是第几条歌词 function showLyric() { //因为歌词头部还有歌手信息,因此高亮的歌词从j开始 var j = lyricArtistMessage.length; $("#lyricDiv ul li").get(j).style.color = "#ff6666"; //检测第第三句歌词,如果是中文,则歌词大小改为18px if(lyricResult.length > 3) { for(var i = 0; i < lyricResult[2][1].length; i++) { //因为歌词英文的分号为’,所以也要排除这个 if(lyricResult[2][1].charCodeAt(i) > 127 && lyricResult[2][1].charAt(i) != '’') { $("#lyricDiv ul").css("font-size", "19px"); $("#lyric").css("font-size", "19px"); } } } $("#audio")[0].ontimeupdate = function(e) { if(lyricResult.length > 0 && this.currentTime > lyricResult[lyricIndex][0]) { //如果大屏歌词没有打开,则小框进行显示 if($("#lyricDiv").css("display") == "none") { $("#lyric").fadeOut(0); $("#lyric").text(lyricResult[lyricIndex][1]); $("#lyric").fadeIn(); } else { $("#lyric").text("歌词"); //把上一条歌词颜色进行恢复 $("#lyricDiv ul li").css("color", "#cbc7c7"); //把现在这条歌词高亮显示 $("#lyricDiv ul li").get(lyricIndex + j).style.color = "#ff6666"; //初始歌词的高度是80,所以将80-叠加的高度则得出歌词需要滑动的高度 $("#lyricDiv ul").animate({ "top": 80 - lyricHeight[lyricIndex - 1] + "px" }, 1000); } //把歌词的索引移到下一条 lyricIndex++; } } } function showFullLyric() { $(".header button").css("display", "none"); $("#back").css("display", "block"); $(".musicList").css("display", "none"); $(".albumPic").css("display", "none"); $("#lyricDiv").css("display", "block"); //把所有歌词的高度保存,因为设为none之后该值也会丢失 //为了方面后面设置高度,保存的值是叠加高度的值 lyricHeight = []; for(var i = 0; i < $("#lyricDiv ul li").length; i++) { var last = i == 0 ? 0 : lyricHeight[i - 1]; lyricHeight.push(last + $("#lyricDiv ul li")[i].offsetHeight); } $("#lyric").text("歌词"); //迅速滑到当前播放的歌词 //初始歌词的高度是80,所以将80-叠加的高度则得出歌词需要滑动的高度 if(lyricIndex >= 2){ $("#lyricDiv ul").animate({ "top": 80 - lyricHeight[lyricIndex - 1] + "px" }, 1000); } } function lyricTip(t, e) { var lyric = document.getElementsByClassName("lyric")[0]; var tooltipTop = lyric.offsetTop; if(e.target.id == "lyric") { var tooltipHtml = "
    点击查看全部歌词
    "; $(t).append(tooltipHtml); //添加到页面中 $("#tooltip").css({ "top": tooltipTop + 30 + "px", "left": "210px", }).show("fast"); //设置提示框的坐标,并显示 } }

    画星空背景的代码为:

    //画星空背景
    function drawStars() {
    	var canvas = document.getElementById('canvas'),
    		ctx = canvas.getContext('2d'),
    		w = canvas.width = window.innerWidth,
    		h = canvas.height = window.innerHeight,
    
    		hue = 217, //色调色彩
    		stars = [], //保存所有星星
    		count = 0,  //用于计算星星
    		maxStars = 1300; //星星数量
    	
    	//canvas2是用来创建星星的源图像,即母版,
    	//根据星星自身属性的大小来设置
    	var canvas2 = document.createElement('canvas'),
    		ctx2 = canvas2.getContext('2d');
    	canvas2.width = 100;
    	canvas2.height = 100;
    	//创建径向渐变,从坐标(half,half)半径为0的圆开始,
    	//到坐标为(half,half)半径为half的圆结束
    	var half = canvas2.width / 2,
    		gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half);
    	gradient2.addColorStop(0.025, '#CCC');
    	//hsl是另一种颜色的表示方式,
    	//h->hue,代表色调色彩,0为red,120为green,240为blue
    	//s->saturation,代表饱和度,0%-100%
    	//l->lightness,代表亮度,0%为black,100%位white
    	gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)');
    	gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)');
    	gradient2.addColorStop(1, 'transparent');
    
    	ctx2.fillStyle = gradient2;
    	ctx2.beginPath();
    	ctx2.arc(half, half, half, 0, Math.PI * 2);
    	ctx2.fill();
    
    	// End cache
    	function random(min, max) {
    		if(arguments.length < 2) {
    			max = min;
    			min = 0;
    		}
    
    		if(min > max) {
    			var hold = max;
    			max = min;
    			min = hold;
    		}
    		
    		//返回min和max之间的一个随机值
    		return Math.floor(Math.random() * (max - min + 1)) + min;
    	}
    
    	function maxOrbit(x, y) {
    		var max = Math.max(x, y),
    			diameter = Math.round(Math.sqrt(max * max + max * max));
    		//星星移动范围,值越大范围越小,
    		return diameter / 2;
    	}
    
    	var Star = function() {
    		//星星移动的半径
    		this.orbitRadius = random(maxOrbit(w, h));
    		//星星大小,半径越小,星星也越小,即外面的星星会比较大
    		this.radius = random(60, this.orbitRadius) / 8;
    		//所有星星都是以屏幕的中心为圆心
    		this.orbitX = w / 2;
    		this.orbitY = h / 2;
    		//星星在旋转圆圈位置的角度,每次增加speed值的角度
    		//利用正弦余弦算出真正的x、y位置
    		this.timePassed = random(0, maxStars);
    		//星星移动速度
    		this.speed = random(this.orbitRadius) / 50000;
    		//星星图像的透明度
    		this.alpha = random(2, 10) / 10;
    
    		count++;
    		stars[count] = this;
    	}
    
    	Star.prototype.draw = function() {
    		//星星围绕在以屏幕中心为圆心,半径为orbitRadius的圆旋转
    		var x = Math.sin(this.timePassed) * this.orbitRadius + this.orbitX,
    			y = Math.cos(this.timePassed) * this.orbitRadius + this.orbitY,
    			twinkle = random(10);
    		
    		//星星每次移动会有1/10的几率变亮或者变暗
    		if(twinkle === 1 && this.alpha > 0) {
    			//透明度降低,变暗
    			this.alpha -= 0.05;
    		} else if(twinkle === 2 && this.alpha < 1) {
    			//透明度升高,变亮
    			this.alpha += 0.05;
    		}
    
    		ctx.globalAlpha = this.alpha;
    		//使用canvas2作为源图像来创建星星,
    		//位置在x - this.radius / 2, y - this.radius / 2
    		//大小为 this.radius
    		ctx.drawImage(canvas2, x - this.radius / 2, y - this.radius / 2, this.radius, this.radius);
    		//没旋转一次,角度就会增加
    		this.timePassed += this.speed;
    	}
    	
    	//初始化所有星星
    	for(var i = 0; i < maxStars; i++) {
    		new Star();
    	}
    
    	function animation() {
    		//以新图像覆盖已有图像的方式进行绘制背景颜色
    		ctx.globalCompositeOperation = 'source-over';
    		ctx.globalAlpha = 0.5; //尾巴
    		ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 2)';
    		ctx.fillRect(0, 0, w, h)
    
    		//源图像和目标图像同时显示,重叠部分叠加颜色效果
    		ctx.globalCompositeOperation = 'lighter';
    		for(var i = 1, l = stars.length; i < l; i++) {
    			stars[i].draw();
    		};
    		
    		//调用该方法执行动画,并且在重绘的时候调用指定的函数来更新动画
    		//回调的次数通常是每秒60次
    		window.requestAnimationFrame(animation);
    	}
    
    	animation();
    }
    
    • 还有音乐列表歌名搜索、网络音乐搜索、自定义div弹出框、列表信息显示、iframe跨域传送信息、播放快捷键绑定等功能,在index.js代码文件中都有很详细的注释,可以自行查看。

    后序

    • 其实在写播放器的时候,是没有一个整体的框架的,基本都是想到什么功能,就添加一个函数,几乎所有脚本代码都写在一个index.js文件中了。这也是后续学习的方向,毕竟软件性能、代码维护都需要良好的框架支撑。
    • 第一次写GitHub,陈述不清楚,还望见谅,继续学习中…
    • Stay Hungry,Stay Foolish. Continue Hello World!

    你可能感兴趣的:(H5)