diff --git a/.gitignore b/.gitignore index 7cf68d0..d984f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ dist .sass-cache app/bower_components .DS_Store - +.idea/ diff --git a/scripts/content.json b/content/index.json similarity index 100% rename from scripts/content.json rename to content/index.json diff --git a/content/images/bg.jpg b/images/bg.jpg similarity index 100% rename from content/images/bg.jpg rename to images/bg.jpg diff --git a/index.html b/index.html index 3102aa6..131af31 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@

- +
    diff --git a/scripts/selected.js b/scripts/selected.js index 336e638..d162d13 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -6,82 +6,41 @@ * view on GitHub:https://github.com/wayou/selected * see the live site:http://wayou.github.io/selected/ * songs used in this project are only for educational purpose + * Changelog: + * By guoapeng from Feb 20th, 2020 + * deeply refactor initializing process + * decoupled AudioPlayer and PlayList with clear responsibility by applying event-driven model + * refer to the branch refactore on https://github.com/guoapeng/Html5AudioPlayer.git for details */ window.onload = function() { new Selected().init(); -}; -var Selected = function() { - this.audio = document.getElementById('audio'); - this.lyricContainer = document.getElementById('lyricContainer'); - this.playlist = document.getElementById('playlist'); - this.currentIndex = 0; - this.lyric = null; - this.lyricStyle = 0; //random num to specify the different class name for lyric -}; -Selected.prototype = { - constructor: Selected, //fix the prototype chain - init: function() { - //get all songs and add to the playlist - this.initialList(this); - - var that = this, - allSongs = this.playlist.children[0].children, - currentSong, randomSong; - - //get the hash from the url if there's any. - var songName = window.location.hash.substr(1); - //then get the index of the song from all songs - var indexOfHashSong = (function() { - var index = 0; - Array.prototype.forEach.call(allSongs, function(v, i, a) { - if (v.children[0].getAttribute('data-name') == songName) { - index = i; - return false; - } - }); - return index; - })(); - - this.currentIndex = indexOfHashSong || Math.floor(Math.random() * allSongs.length); - - currentSong = allSongs[this.currentIndex]; - randomSong = currentSong.children[0].getAttribute('data-name'); +} - //set the song name to the hash of the url - window.location.hash = window.location.hash || randomSong; - - - //handle playlist - this.playlist.addEventListener('click', function(e) { - if (e.target.nodeName.toLowerCase() !== 'a') { - return; - }; - var allSongs = that.playlist.children[0].children, - selectedIndex = Array.prototype.indexOf.call(allSongs, e.target.parentNode); - that.currentIndex = selectedIndex; - that.setClass(selectedIndex); - var songName = e.target.getAttribute('data-name'); - window.location.hash = songName; - that.play(songName); - }, false); - this.audio.onended = function() { - that.playNext(that); - } - this.audio.onerror = function(e) { - that.lyricContainer.textContent = '!fail to load the song :('; - }; +function Selected() { + this.audioPlayer = new AudioPlayer(document.getElementById('audio')) + this.subtitleManager = new SubtitleManager(document.getElementById('lyricContainer')); + this.playlist = new PlayList(document.getElementById('playlist')); +} +Selected.prototype = { + getAudio: function() { + return this.audioPlayer.getAudioContainer() + }, + init: function() { + var that = this; + this.audioPlayer.init(this.subtitleManager.getPlayerTimeUpdateHandler(), this.subtitleManager.getPlayerErrorHandler()); + this.subtitleManager.init(); + this.playlist.init('./content/index.json'); //enable keyboard control , spacebar to play and pause window.addEventListener('keydown', function(e) { if (e.keyCode === 32) { - if (that.audio.paused) { - that.audio.play(); + if (that.getAudio().paused) { + that.getAudio().play(); } else { - that.audio.pause(); + that.getAudio().pause(); } } }, false); - //initialize the background setting document.getElementById('bg_dark').addEventListener('click', function() { document.getElementsByTagName('html')[0].className = 'colorBg'; @@ -89,93 +48,111 @@ Selected.prototype = { document.getElementById('bg_pic').addEventListener('click', function() { document.getElementsByTagName('html')[0].className = 'imageBg'; }); - //initially start from a random song - for (var i = allSongs.length - 1; i >= 0; i--) { - allSongs[i].className = ''; + } +} + +function AudioPlayer(audioContainer, lyricContainer) { + this.audioContainer = audioContainer + +} + +AudioPlayer.prototype = { + init: function(onTimeUpdate, onPlayerError){ + var that = this + this.audioContainer.onended = function() { + window.dispatchEvent(new Event("audioFinished")) + }; + this.audioContainer.onerror = function(e) { + onPlayerError(e); }; - currentSong.className = 'current-song'; - this.play(randomSong); + this.audioContainer.addEventListener("timeupdate", function(e) { + onTimeUpdate(e, that.audioContainer.currentTime) + }); + window.addEventListener("playAudio", function(e){ + that.play('./content/songs/' + e.audioName + '.mp3'); + }); }, - initialList: function(ctx) { - var xhttp = new XMLHttpRequest(); - xhttp.open('GET', './scripts/content.json', false); - xhttp.onreadystatechange = function() { - if (xhttp.status == 200 && xhttp.readyState == 4) { - var fragment = document.createDocumentFragment(), - data = JSON.parse(xhttp.responseText).data, - ol = ctx.playlist.getElementsByTagName('ol')[0], - fragment = document.createDocumentFragment(); + getAudioContainer: function () { + return this.audioContainer + }, + play: function(mediaUrl) { + this.getAudioContainer().addEventListener('canplay', function() { + this.play(); + }); + this.getAudioContainer().src = mediaUrl; + } +} - data.forEach(function(v, i, a) { - var li = document.createElement('li'), - a = document.createElement('a'); - a.href = 'javascript:void(0)'; - a.dataset.name = v.lrc_name; - a.textContent = v.song_name + '-' + v.artist; - li.appendChild(a); - fragment.appendChild(li); - }); - ol.appendChild(fragment); - } - }; - xhttp.send(); +function SubtitleManager(subtitleContainer){ + this.subtitleContainer = subtitleContainer; + this.lyric = null; + this.lyricStyle = 0; //random num to specify the different class name for lyric +} + +SubtitleManager.prototype = { + getSubtitleContainer: function () { + return this.subtitleContainer; }, - play: function(songName) { + init: function(){ var that = this; - this.audio.src = './content/songs/' + songName + '.mp3'; + window.addEventListener("playAudio", function(e){ + that.loadLyric( './content/songs/' + e.audioName + '.lrc'); + }); + }, + reset: function () { //reset the position of the lyric container - this.lyricContainer.style.top = '130px'; + this.getSubtitleContainer().style.top = '130px'; + this.getSubtitleContainer().textContent = 'loading...'; //empty the lyric - this.lyric = null; - this.lyricContainer.textContent = 'loading...'; + this.setLyricText(null) this.lyricStyle = Math.floor(Math.random() * 4); - this.audio.addEventListener('canplay', function() { - that.getLyric(that.audio.src.replace('.mp3', '.lrc')); - this.play(); - }); - //sync the lyric - this.audio.addEventListener("timeupdate", function(e) { - if (!that.lyric) return; - for (var i = 0, l = that.lyric.length; i < l; i++) { - if (this.currentTime > that.lyric[i][0] - 0.50 /*preload the lyric by 0.50s*/ ) { - //single line display mode - // that.lyricContainer.textContent = that.lyric[i][1]; - //scroll mode - var line = document.getElementById('line-' + i), - prevLine = document.getElementById('line-' + (i > 0 ? i - 1 : i)); - prevLine.className = ''; - //randomize the color of the current line of the lyric - line.className = 'current-line-' + that.lyricStyle; - that.lyricContainer.style.top = 130 - line.offsetTop + 'px'; - }; - }; - }); }, - playNext: function(that) { - var allSongs = this.playlist.children[0].children, - nextItem; - //reaches the last song of the playlist? - if (that.currentIndex === allSongs.length - 1) { - //play from start - that.currentIndex = 0; - } else { - //play next index - that.currentIndex += 1; - }; - nextItem = allSongs[that.currentIndex].children[0]; - that.setClass(that.currentIndex); - var songName = nextItem.getAttribute('data-name'); - window.location.hash = songName; - that.play(songName); + getLyricText: function(){ + return this.lyric }, - setClass: function(index) { - var allSongs = this.playlist.children[0].children; - for (var i = allSongs.length - 1; i >= 0; i--) { - allSongs[i].className = ''; + setLyricText: function(lyric){ + this.lyric = lyric + }, + getPlayerErrorHandler: function(){ + var that = this; + return function(e){ + that.getSubtitleContainer().textContent = '!fail to load the song :('; + } + }, + getPlayerTimeUpdateHandler: function(){ + var that = this; + return function (e, currentTime) { + that.synchronizeLyric(e, currentTime); + } + }, + synchronizeLyric: function(e, currentTime) { + if (!this.getLyricText()) return; + for (var i = 0, l = this.getLyricText().length; i < l; i++) { + if (currentTime > this.getLyricText()[i][0] - 0.50 /*preload the lyric by 0.50s*/ ) { + //scroll mode + var line = document.getElementById('line-' + i), + prevLine = document.getElementById('line-' + (i > 0 ? i - 1 : i)); + prevLine.className = ''; + //randomize the color of the current line of the lyric + line.className = 'current-line-' + this.lyricStyle; + this.getSubtitleContainer().style.top = 130 - line.offsetTop + 'px'; + }; }; - allSongs[index].className = 'current-song'; }, - getLyric: function(url) { + appendLyric: function(lyric) { + var lyricContainer = this.getSubtitleContainer(), + fragment = document.createDocumentFragment(); + //clear the lyric container first + lyricContainer.innerHTML = ''; + lyric.forEach(function(v, i) { + var line = document.createElement('p'); + line.id = 'line-' + i; + line.textContent = v[1]; + fragment.appendChild(line); + }); + lyricContainer.appendChild(fragment); + }, + loadLyric: function(url) { var that = this, request = new XMLHttpRequest(); request.open('GET', url, true); @@ -183,14 +160,14 @@ Selected.prototype = { //fix for the messy code problem for Chinese. reference: http://xx.time8.org/php/20101218/ajax-xmlhttprequest.html //request['overrideMimeType'] && request.overrideMimeType("text/html;charset=gb2312"); request.onload = function() { - that.lyric = that.parseLyric(request.response); + that.setLyricText(that.parseLyric(request.response)); //display lyric to the page - that.appendLyric(that.lyric); + that.appendLyric(that.getLyricText()); }; request.onerror = request.onabort = function(e) { - that.lyricContainer.textContent = '!failed to load the lyric :('; - } - this.lyricContainer.textContent = 'loading lyric...'; + that.getSubtitleContainer().textContent = '!failed to load the lyric :('; + }; + that.reset(); request.send(); }, parseLyric: function(text) { @@ -199,7 +176,6 @@ Selected.prototype = { //this regex mathes the time [00.12.78] pattern = /\[\d{2}:\d{2}.\d{2}\]/g, result = []; - // Get offset from lyrics var offset = this.getOffset(text); @@ -213,7 +189,7 @@ Selected.prototype = { lines.forEach(function(v, i, a) { var time = v.match(pattern), value = v.replace(pattern, ''); - time.forEach(function(v1, i1, a1) { + time.forEach(function(v1) { //convert the [min:sec] to secs format then store into result var t = v1.slice(1, -1).split(':'); result.push([parseInt(t[0], 10) * 60 + parseFloat(t[1]) + parseInt(offset) / 1000, value]); @@ -223,23 +199,8 @@ Selected.prototype = { result.sort(function(a, b) { return a[0] - b[0]; }); - console.log(result); return result; }, - appendLyric: function(lyric) { - var that = this, - lyricContainer = this.lyricContainer, - fragment = document.createDocumentFragment(); - //clear the lyric container first - this.lyricContainer.innerHTML = ''; - lyric.forEach(function(v, i, a) { - var line = document.createElement('p'); - line.id = 'line-' + i; - line.textContent = v[1]; - fragment.appendChild(line); - }); - lyricContainer.appendChild(fragment); - }, getOffset: function(text) { //Returns offset in miliseconds. var offset = 0; @@ -253,9 +214,131 @@ Selected.prototype = { // Convert it to Int. offset = parseInt(offset_str); } catch (err) { - //alert("offset error: "+err.message); offset = 0; } return offset; + }, +} + +function PlayList(playListContainer){ + this.container = playListContainer; + this.currentIndex=0 +} + +PlayList.prototype = { + setCurrentIndex: function(index){ + this.currentIndex = index + }, + getContainer:function(){ + return this.container; + }, + getAllSongs: function() { + return this.container.children[0].children; + }, + getCurrentSong: function() { + return this.getAllSongs()[this.currentIndex]; + }, + setClass: function() { + var allSongs = this.getAllSongs() + for (var i = allSongs.length - 1; i >= 0; i--) { + if(allSongs[i].className) allSongs[i].className = ''; + }; + this.getCurrentSong().className = 'current-song'; + }, + moveToNext: function(){ + //reaches the last song of the playlist? + if (this.currentIndex === this.getAllSongs().length - 1) { + //play from start + this.currentIndex = 0 + } else { + //play next index + this.currentIndex = this.currentIndex + 1 + }; + this.setClass(); + var songName = this.getCurrentSong().children[0].getAttribute('data-name'); + window.location.hash = songName; + var playAudioEvent = new Event("playAudio"); + playAudioEvent.audioName = songName; + window.dispatchEvent(playAudioEvent) + }, + init: function(contentUrl) { + var playList = this + window.addEventListener("playlistReady", function(){ + playList.autoPlay(); + }); + window.addEventListener("audioFinished", function(){ + playList.moveToNext(); + }); + //get all songs and add to the playlist + var xhttp = new XMLHttpRequest(); + xhttp.open('GET', contentUrl, false); + xhttp.onreadystatechange = function() { + if (xhttp.status == 200 && xhttp.readyState == 4) { + var data = JSON.parse(xhttp.responseText).data + playList.refreshPlayList(data) + window.dispatchEvent(new Event("playlistReady")) + } + }; + xhttp.send(); + this.handleClickEvent(); + }, + autoPlay: function() { + //get the hash from the url if there's any. + var songName = window.location.hash.substr(1); + //then get the index of the song from all songs + var indexOfHashSong = this.getSongIndex(songName); + this.setCurrentIndex(indexOfHashSong || Math.floor(Math.random() * this.getAllSongs().length)); + //set the song name to the hash of the url + window.location.hash = window.location.hash || this.getCurrentSong().children[0].getAttribute('data-name'); + this.setClass() + var playAudioEvent = new Event("playAudio"); + playAudioEvent.audioName = this.getCurrentSong().children[0].getAttribute('data-name'); + window.dispatchEvent(playAudioEvent) + }, + refreshPlayList: function(data) { + var playList = this, + ol = this.container.getElementsByTagName('ol')[0], + fragment = document.createDocumentFragment(); + data.forEach(function(v) { + fragment.appendChild(playList.createPlayListItem(v)); + }); + ol.appendChild(fragment); + }, + createPlayListItem: function (audioDetail){ + var li = document.createElement('li'), + a = document.createElement('a'); + a.href = 'javascript:void(0)'; + a.dataset.name = audioDetail.lrc_name; + a.textContent = audioDetail.song_name + '-' + audioDetail.artist; + li.appendChild(a); + return li + }, + handleClickEvent: function(){ + //handle playlist + var playList = this + this.getContainer().addEventListener('click', function(e) { + if (e.target.nodeName.toLowerCase() !== 'a') { + return; + }; + var allSongs = playList.getAllSongs(), + selectedIndex = Array.prototype.indexOf.call(allSongs, e.target.parentNode); + playList.setCurrentIndex(selectedIndex); + playList.setClass(); + var songName = e.target.getAttribute('data-name'); + window.location.hash = songName; + var playAudioEvent = new Event("playAudio"); + playAudioEvent.audioName = songName; + window.dispatchEvent(playAudioEvent) + }, false); + }, + getSongIndex: function(songName) { + var index = 0; + Array.prototype.forEach.call(this.getAllSongs(), function(v, i, a) { + if (v.children[0].getAttribute('data-name') == songName) { + index = i; + return false; + } + }); + return index; } -}; +} diff --git a/styles/style.css b/styles/style.css index 9c05871..bf55cb4 100644 --- a/styles/style.css +++ b/styles/style.css @@ -32,7 +32,7 @@ a:hover { text-decoration: underline; } .imageBg { - background: url(../content/images/bg.jpg) no-repeat center center fixed; + background: url(../images/bg.jpg) no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover;