From b7430b0edc63dbfa60a4efcdbe7742bf1764af1a Mon Sep 17 00:00:00 2001 From: guoapeng Date: Wed, 19 Feb 2020 02:16:03 +0000 Subject: [PATCH 1/5] deep refactore initializing process --- .gitignore | 2 +- scripts/selected.js | 150 ++++++++++++++++++++++++++------------------ 2 files changed, 90 insertions(+), 62 deletions(-) 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/selected.js b/scripts/selected.js index 336e638..5c6c448 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -9,61 +9,41 @@ */ window.onload = function() { new Selected().init(); -}; -var Selected = function() { +} +function Selected() { this.audio = document.getElementById('audio'); this.lyricContainer = document.getElementById('lyricContainer'); - this.playlist = document.getElementById('playlist'); + this.playlist = new PlayList(document.getElementById('playlist'), this); 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 + getPlayList: function() { + return this.playlist + }, init: function() { //get all songs and add to the playlist - this.initialList(this); - + this.playlist.initialList('./scripts/content.json'); 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; - })(); + var indexOfHashSong = this.playlist.getSongIndex(songName); - this.currentIndex = indexOfHashSong || Math.floor(Math.random() * allSongs.length); + this.currentIndex = indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length); - currentSong = allSongs[this.currentIndex]; + currentSong = that.playlist.getAllSongs()[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.playlist.handleClickEvent(); + this.audio.onended = function() { that.playNext(that); } @@ -90,36 +70,12 @@ Selected.prototype = { document.getElementsByTagName('html')[0].className = 'imageBg'; }); //initially start from a random song - for (var i = allSongs.length - 1; i >= 0; i--) { - allSongs[i].className = ''; + for (var i = that.playlist.getAllSongs().length - 1; i >= 0; i--) { + that.playlist.getAllSongs()[i].className = ''; }; currentSong.className = 'current-song'; this.play(randomSong); }, - 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(); - - 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(); - }, play: function(songName) { var that = this; this.audio.src = './content/songs/' + songName + '.mp3'; @@ -152,7 +108,7 @@ Selected.prototype = { }); }, playNext: function(that) { - var allSongs = this.playlist.children[0].children, + var allSongs = this.playlist.getContainer().children[0].children, nextItem; //reaches the last song of the playlist? if (that.currentIndex === allSongs.length - 1) { @@ -169,7 +125,7 @@ Selected.prototype = { that.play(songName); }, setClass: function(index) { - var allSongs = this.playlist.children[0].children; + var allSongs = this.playlist.getContainer().children[0].children; for (var i = allSongs.length - 1; i >= 0; i--) { allSongs[i].className = ''; }; @@ -259,3 +215,75 @@ Selected.prototype = { return offset; } }; + +function PlayList(playListContainer, player){ + this.container = playListContainer; + this.player = player +} + +PlayList.prototype = { + getContainer:function(){ + return this.container; + }, + getAllSongs: function() { + return this.container.children[0].children; + }, + initialList: function(contentUrl) { + 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 + refreshPlayList(this.container, data) + } + }; + xhttp.send(); + }, + 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.player.currentIndex = selectedIndex; + playList.player.setClass(selectedIndex); + var songName = e.target.getAttribute('data-name'); + window.location.hash = songName; + playList.player.play(songName); + }, 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; + } +} + +function refreshPlayList(playList, data) { + var ol = playlist.getElementsByTagName('ol')[0], + fragment = document.createDocumentFragment(); + + data.forEach(function(v) { + fragment.appendChild(createPlayListItem(v)); + }); + ol.appendChild(fragment); +} + +function createPlayListItem(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 +} + From 8057f17c5d3b6fd865b9edd8fab73ccbcd0f2e2e Mon Sep 17 00:00:00 2001 From: guoapeng Date: Wed, 19 Feb 2020 17:08:21 +0000 Subject: [PATCH 2/5] move properties to AudioPlayer object --- scripts/selected.js | 146 ++++++++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 59 deletions(-) diff --git a/scripts/selected.js b/scripts/selected.js index 5c6c448..676e5a2 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -11,17 +11,29 @@ window.onload = function() { new Selected().init(); } function Selected() { - this.audio = document.getElementById('audio'); - this.lyricContainer = document.getElementById('lyricContainer'); + this.audioPlayer = new AudioPlayer(document.getElementById('audio'), document.getElementById('lyricContainer')) this.playlist = new PlayList(document.getElementById('playlist'), this); - 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 - getPlayList: function() { - return this.playlist + getAudio: function() { + return this.audioPlayer.getAudioContainer() + }, + getLyricContainer: function(){ + return this.audioPlayer.getLyricContainer() + }, + getLyricText: function(){ + return this.audioPlayer.lyric + }, + setLyricText: function(lyric){ + this.audioPlayer.lyric = lyric + }, + getCurrentIndex: function(){ + return this.playlist.currentIndex; + }, + setCurrentIndex: function(index){ + this.playlist.currentIndex = index }, init: function() { //get all songs and add to the playlist @@ -33,9 +45,9 @@ Selected.prototype = { //then get the index of the song from all songs var indexOfHashSong = this.playlist.getSongIndex(songName); - this.currentIndex = indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length); + this.setCurrentIndex(indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length)); - currentSong = that.playlist.getAllSongs()[this.currentIndex]; + currentSong = that.playlist.getAllSongs()[this.getCurrentIndex()]; randomSong = currentSong.children[0].getAttribute('data-name'); //set the song name to the hash of the url @@ -44,20 +56,20 @@ Selected.prototype = { //handle playlist this.playlist.handleClickEvent(); - this.audio.onended = function() { + this.getAudio().onended = function() { that.playNext(that); } - this.audio.onerror = function(e) { - that.lyricContainer.textContent = '!fail to load the song :('; + this.getAudio().onerror = function(e) { + that.getLyricContainer().textContent = '!fail to load the song :('; }; //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); @@ -74,26 +86,27 @@ Selected.prototype = { that.playlist.getAllSongs()[i].className = ''; }; currentSong.className = 'current-song'; + console.info("playing song:"+randomSong) this.play(randomSong); }, play: function(songName) { var that = this; - this.audio.src = './content/songs/' + songName + '.mp3'; + this.getAudio().src = './content/songs/' + songName + '.mp3'; //reset the position of the lyric container - this.lyricContainer.style.top = '130px'; + this.getLyricContainer().style.top = '130px'; //empty the lyric - this.lyric = null; - this.lyricContainer.textContent = 'loading...'; + this.setLyricText(null) + this.getLyricContainer().textContent = 'loading...'; this.lyricStyle = Math.floor(Math.random() * 4); - this.audio.addEventListener('canplay', function() { - that.getLyric(that.audio.src.replace('.mp3', '.lrc')); - this.play(); + this.getAudio().addEventListener('canplay', function() { + that.getLyric('./content/songs/' + songName + '.lrc'); + that.getAudio().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*/ ) { + this.getAudio().addEventListener("timeupdate", function(e) { + if (!that.getLyricText()) return; + for (var i = 0, l = that.getLyricText().length; i < l; i++) { + if (this.currentTime > that.getLyricText()[i][0] - 0.50 /*preload the lyric by 0.50s*/ ) { //single line display mode // that.lyricContainer.textContent = that.lyric[i][1]; //scroll mode @@ -102,7 +115,7 @@ Selected.prototype = { 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'; + that.getLyricContainer().style.top = 130 - line.offsetTop + 'px'; }; }; }); @@ -111,15 +124,15 @@ Selected.prototype = { var allSongs = this.playlist.getContainer().children[0].children, nextItem; //reaches the last song of the playlist? - if (that.currentIndex === allSongs.length - 1) { + if (that.getCurrentIndex() === allSongs.length - 1) { //play from start - that.currentIndex = 0; + that.setCurrentIndex(0) } else { //play next index - that.currentIndex += 1; + that.setCurrentIndex(that.getCurrentIndex()+1); }; - nextItem = allSongs[that.currentIndex].children[0]; - that.setClass(that.currentIndex); + nextItem = allSongs[that.getCurrentIndex()].children[0]; + that.setClass(that.getCurrentIndex()); var songName = nextItem.getAttribute('data-name'); window.location.hash = songName; that.play(songName); @@ -139,14 +152,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 :('; + that.getLyricContainer().textContent = '!failed to load the lyric :('; } - this.lyricContainer.textContent = 'loading lyric...'; + this.getLyricContainer().textContent = 'loading lyric...'; request.send(); }, parseLyric: function(text) { @@ -179,15 +192,14 @@ 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, + lyricContainer = this.getLyricContainer(), fragment = document.createDocumentFragment(); //clear the lyric container first - this.lyricContainer.innerHTML = ''; + lyricContainer.innerHTML = ''; lyric.forEach(function(v, i, a) { var line = document.createElement('p'); line.id = 'line-' + i; @@ -216,9 +228,25 @@ Selected.prototype = { } }; +function AudioPlayer(audioContainer, lyricContainer) { + this.audioContainer = audioContainer + this.lyricContainer = lyricContainer + this.lyric = null; +} +AudioPlayer.prototype = { + getAudioContainer: function () { + return this.audioContainer + }, + getLyricContainer: function () { + return this.lyricContainer + } +} + + function PlayList(playListContainer, player){ this.container = playListContainer; this.player = player + this.currentIndex=0 } PlayList.prototype = { @@ -229,16 +257,35 @@ PlayList.prototype = { return this.container.children[0].children; }, initialList: function(contentUrl) { + var playList = this 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 - refreshPlayList(this.container, data) + playList.refreshPlayList(data) } }; xhttp.send(); }, + 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 @@ -248,7 +295,7 @@ PlayList.prototype = { }; var allSongs = playList.getAllSongs(), selectedIndex = Array.prototype.indexOf.call(allSongs, e.target.parentNode); - playList.player.currentIndex = selectedIndex; + playList.player.setCurrentIndex(selectedIndex); playList.player.setClass(selectedIndex); var songName = e.target.getAttribute('data-name'); window.location.hash = songName; @@ -267,23 +314,4 @@ PlayList.prototype = { } } -function refreshPlayList(playList, data) { - var ol = playlist.getElementsByTagName('ol')[0], - fragment = document.createDocumentFragment(); - - data.forEach(function(v) { - fragment.appendChild(createPlayListItem(v)); - }); - ol.appendChild(fragment); -} - -function createPlayListItem(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 -} From 27ebc84dacab2ade108dfece802ab54f85b4792d Mon Sep 17 00:00:00 2001 From: guoapeng Date: Thu, 20 Feb 2020 19:50:22 +0000 Subject: [PATCH 3/5] continue on refactore playlist --- scripts/selected.js | 142 +++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/scripts/selected.js b/scripts/selected.js index 676e5a2..7d84fb7 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -23,35 +23,20 @@ Selected.prototype = { getLyricContainer: function(){ return this.audioPlayer.getLyricContainer() }, - getLyricText: function(){ - return this.audioPlayer.lyric - }, - setLyricText: function(lyric){ - this.audioPlayer.lyric = lyric - }, - getCurrentIndex: function(){ - return this.playlist.currentIndex; - }, - setCurrentIndex: function(index){ - this.playlist.currentIndex = index - }, + init: function() { //get all songs and add to the playlist - this.playlist.initialList('./scripts/content.json'); - var that = this, - currentSong, randomSong; + this.playlist.init('./scripts/content.json'); + var that = this; //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.playlist.getSongIndex(songName); - this.setCurrentIndex(indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length)); - - currentSong = that.playlist.getAllSongs()[this.getCurrentIndex()]; - randomSong = currentSong.children[0].getAttribute('data-name'); + this.playlist.setCurrentIndex(indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length)); //set the song name to the hash of the url - window.location.hash = window.location.hash || randomSong; + window.location.hash = window.location.hash || that.playlist.getCurrentSong().children[0].getAttribute('data-name'); //handle playlist this.playlist.handleClickEvent(); @@ -59,6 +44,7 @@ Selected.prototype = { this.getAudio().onended = function() { that.playNext(that); } + this.getAudio().onerror = function(e) { that.getLyricContainer().textContent = '!fail to load the song :('; }; @@ -85,9 +71,9 @@ Selected.prototype = { for (var i = that.playlist.getAllSongs().length - 1; i >= 0; i--) { that.playlist.getAllSongs()[i].className = ''; }; - currentSong.className = 'current-song'; - console.info("playing song:"+randomSong) - this.play(randomSong); + that.playlist.getCurrentSong().className = 'current-song'; + + //this.play(randomSong); }, play: function(songName) { var that = this; @@ -95,18 +81,18 @@ Selected.prototype = { //reset the position of the lyric container this.getLyricContainer().style.top = '130px'; //empty the lyric - this.setLyricText(null) + this.audioPlayer.setLyricText(null) this.getLyricContainer().textContent = 'loading...'; this.lyricStyle = Math.floor(Math.random() * 4); this.getAudio().addEventListener('canplay', function() { - that.getLyric('./content/songs/' + songName + '.lrc'); + that.audioPlayer.loadLyric('./content/songs/' + songName + '.lrc'); that.getAudio().play(); }); //sync the lyric this.getAudio().addEventListener("timeupdate", function(e) { - if (!that.getLyricText()) return; - for (var i = 0, l = that.getLyricText().length; i < l; i++) { - if (this.currentTime > that.getLyricText()[i][0] - 0.50 /*preload the lyric by 0.50s*/ ) { + if (!that.audioPlayer.getLyricText()) return; + for (var i = 0, l = that.audioPlayer.getLyricText().length; i < l; i++) { + if (this.currentTime > that.audioPlayer.getLyricText()[i][0] - 0.50 /*preload the lyric by 0.50s*/ ) { //single line display mode // that.lyricContainer.textContent = that.lyric[i][1]; //scroll mode @@ -121,43 +107,54 @@ Selected.prototype = { }); }, playNext: function(that) { - var allSongs = this.playlist.getContainer().children[0].children, - nextItem; - //reaches the last song of the playlist? - if (that.getCurrentIndex() === allSongs.length - 1) { - //play from start - that.setCurrentIndex(0) - } else { - //play next index - that.setCurrentIndex(that.getCurrentIndex()+1); - }; - nextItem = allSongs[that.getCurrentIndex()].children[0]; - that.setClass(that.getCurrentIndex()); - var songName = nextItem.getAttribute('data-name'); + this.playlist.moveToNext(); + this.playlist.setClass(); + var songName = this.playlist.getCurrentSong().children[0].getAttribute('data-name'); window.location.hash = songName; that.play(songName); }, setClass: function(index) { - var allSongs = this.playlist.getContainer().children[0].children; + var allSongs = this.playlist.getAllSongs(); for (var i = allSongs.length - 1; i >= 0; i--) { - allSongs[i].className = ''; + if(allSongs[i].className) allSongs[i].className = ''; }; allSongs[index].className = 'current-song'; + } +}; + +function AudioPlayer(audioContainer, lyricContainer) { + this.audioContainer = audioContainer + this.lyricContainer = lyricContainer + this.lyric = null; +} + +AudioPlayer.prototype = { + getAudioContainer: function () { + return this.audioContainer + }, + getLyricContainer: function () { + return this.lyricContainer + }, + getLyricText: function(){ + return this.lyric + }, + setLyricText: function(lyric){ + this.lyric = lyric }, - getLyric: function(url) { - var that = this, + loadLyric: function(url) { + var audioPlayer = this, request = new XMLHttpRequest(); request.open('GET', url, true); request.responseType = 'text'; //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.setLyricText(that.parseLyric(request.response)); + audioPlayer.setLyricText(audioPlayer.parseLyric(request.response)); //display lyric to the page - that.appendLyric(that.getLyricText()); + audioPlayer.appendLyric(audioPlayer.getLyricText()); }; request.onerror = request.onabort = function(e) { - that.getLyricContainer().textContent = '!failed to load the lyric :('; + audioPlayer.getLyricContainer().textContent = '!failed to load the lyric :('; } this.getLyricContainer().textContent = 'loading lyric...'; request.send(); @@ -182,7 +179,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]); @@ -195,12 +192,11 @@ Selected.prototype = { return result; }, appendLyric: function(lyric) { - var that = this, - lyricContainer = this.getLyricContainer(), + var lyricContainer = this.getLyricContainer(), fragment = document.createDocumentFragment(); //clear the lyric container first lyricContainer.innerHTML = ''; - lyric.forEach(function(v, i, a) { + lyric.forEach(function(v, i) { var line = document.createElement('p'); line.id = 'line-' + i; line.textContent = v[1]; @@ -221,25 +217,10 @@ Selected.prototype = { // Convert it to Int. offset = parseInt(offset_str); } catch (err) { - //alert("offset error: "+err.message); offset = 0; } return offset; } -}; - -function AudioPlayer(audioContainer, lyricContainer) { - this.audioContainer = audioContainer - this.lyricContainer = lyricContainer - this.lyric = null; -} -AudioPlayer.prototype = { - getAudioContainer: function () { - return this.audioContainer - }, - getLyricContainer: function () { - return this.lyricContainer - } } @@ -250,13 +231,36 @@ function PlayList(playListContainer, player){ } PlayList.prototype = { + setCurrentIndex: function(index){ + this.currentIndex = index + }, getContainer:function(){ return this.container; }, getAllSongs: function() { return this.container.children[0].children; }, - initialList: function(contentUrl) { + getCurrentSong: function() { + 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 + }; + }, + init: function(contentUrl) { var playList = this var xhttp = new XMLHttpRequest(); xhttp.open('GET', contentUrl, false); @@ -295,7 +299,7 @@ PlayList.prototype = { }; var allSongs = playList.getAllSongs(), selectedIndex = Array.prototype.indexOf.call(allSongs, e.target.parentNode); - playList.player.setCurrentIndex(selectedIndex); + playList.setCurrentIndex(selectedIndex); playList.player.setClass(selectedIndex); var songName = e.target.getAttribute('data-name'); window.location.hash = songName; From 778fdd32f5f15d980dcc914c4b8978761defaac1 Mon Sep 17 00:00:00 2001 From: guoapeng Date: Sat, 22 Feb 2020 10:06:53 +0000 Subject: [PATCH 4/5] decouple playlist and audioplayer --- scripts/selected.js | 107 ++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/scripts/selected.js b/scripts/selected.js index 7d84fb7..f137816 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -16,38 +16,21 @@ function Selected() { this.lyricStyle = 0; //random num to specify the different class name for lyric } Selected.prototype = { - constructor: Selected, //fix the prototype chain getAudio: function() { return this.audioPlayer.getAudioContainer() }, getLyricContainer: function(){ return this.audioPlayer.getLyricContainer() }, - init: function() { + + var that = this; //get all songs and add to the playlist this.playlist.init('./scripts/content.json'); - var that = this; - //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.playlist.getSongIndex(songName); - - this.playlist.setCurrentIndex(indexOfHashSong || Math.floor(Math.random() * that.playlist.getAllSongs().length)); - - //set the song name to the hash of the url - window.location.hash = window.location.hash || that.playlist.getCurrentSong().children[0].getAttribute('data-name'); - - //handle playlist - this.playlist.handleClickEvent(); - - this.getAudio().onended = function() { + this.audioPlayer.init(); + window.addEventListener("playnext", function(){ that.playNext(that); - } - - this.getAudio().onerror = function(e) { - that.getLyricContainer().textContent = '!fail to load the song :('; - }; + }) //enable keyboard control , spacebar to play and pause window.addEventListener('keydown', function(e) { @@ -67,27 +50,13 @@ Selected.prototype = { document.getElementById('bg_pic').addEventListener('click', function() { document.getElementsByTagName('html')[0].className = 'imageBg'; }); - //initially start from a random song - for (var i = that.playlist.getAllSongs().length - 1; i >= 0; i--) { - that.playlist.getAllSongs()[i].className = ''; - }; - that.playlist.getCurrentSong().className = 'current-song'; - //this.play(randomSong); }, play: function(songName) { var that = this; - this.getAudio().src = './content/songs/' + songName + '.mp3'; - //reset the position of the lyric container - this.getLyricContainer().style.top = '130px'; - //empty the lyric - this.audioPlayer.setLyricText(null) - this.getLyricContainer().textContent = 'loading...'; + this.audioPlayer.reset() this.lyricStyle = Math.floor(Math.random() * 4); - this.getAudio().addEventListener('canplay', function() { - that.audioPlayer.loadLyric('./content/songs/' + songName + '.lrc'); - that.getAudio().play(); - }); + this.audioPlayer.play('./content/songs/' + songName + '.mp3', './content/songs/' + songName + '.lrc') //sync the lyric this.getAudio().addEventListener("timeupdate", function(e) { if (!that.audioPlayer.getLyricText()) return; @@ -112,13 +81,6 @@ Selected.prototype = { var songName = this.playlist.getCurrentSong().children[0].getAttribute('data-name'); window.location.hash = songName; that.play(songName); - }, - setClass: function(index) { - var allSongs = this.playlist.getAllSongs(); - for (var i = allSongs.length - 1; i >= 0; i--) { - if(allSongs[i].className) allSongs[i].className = ''; - }; - allSongs[index].className = 'current-song'; } }; @@ -129,6 +91,22 @@ function AudioPlayer(audioContainer, lyricContainer) { } AudioPlayer.prototype = { + init: function(){ + this.audioContainer.onended = function() { + window.dispatchEvent(new Event("playnext")) + } + this.audioContainer.onerror = function(e) { + this.lyricContainer.textContent = '!fail to load the song :('; + }; + + }, + reset: function(){ + //reset the position of the lyric container + this.lyricContainer.style.top = '130px'; + this.lyricContainer.textContent = 'loading...'; + //empty the lyric + this.setLyricText(null) + }, getAudioContainer: function () { return this.audioContainer }, @@ -141,6 +119,21 @@ AudioPlayer.prototype = { setLyricText: function(lyric){ this.lyric = lyric }, + addEventListener: function(event){ + var audioPlayer = this + this.getAudioContainer().addEventListener(event.eventName, function() { + audioPlayer.loadLyric('./content/songs/' + event.songName + '.lrc'); + this.play(); + }); + }, + play: function(mediaUrl, lyricUrl) { + var audioPlayer = this + this.getAudioContainer().addEventListener('canplay', function() { + audioPlayer.loadLyric(lyricUrl); + this.play(); + }); + this.getAudioContainer().src = mediaUrl + }, loadLyric: function(url) { var audioPlayer = this, request = new XMLHttpRequest(); @@ -223,7 +216,6 @@ AudioPlayer.prototype = { } } - function PlayList(playListContainer, player){ this.container = playListContainer; this.player = player @@ -241,7 +233,7 @@ PlayList.prototype = { return this.container.children[0].children; }, getCurrentSong: function() { - this.getAllSongs()[this.currentIndex]; + return this.getAllSongs()[this.currentIndex]; }, setClass: function() { var allSongs = this.getAllSongs() @@ -262,15 +254,34 @@ PlayList.prototype = { }, init: function(contentUrl) { var playList = this + window.addEventListener("playlistReady", function(){ + playList.autoPlay(); + }); 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() + this.player.play(this.getCurrentSong().children[0].getAttribute('data-name')); }, refreshPlayList: function(data) { var playList = this, @@ -300,7 +311,7 @@ PlayList.prototype = { var allSongs = playList.getAllSongs(), selectedIndex = Array.prototype.indexOf.call(allSongs, e.target.parentNode); playList.setCurrentIndex(selectedIndex); - playList.player.setClass(selectedIndex); + playList.setClass(); var songName = e.target.getAttribute('data-name'); window.location.hash = songName; playList.player.play(songName); From a5070a1d99ecf50caba2b6ffae5a2c40676d1851 Mon Sep 17 00:00:00 2001 From: guoapeng Date: Sat, 22 Feb 2020 22:38:47 +0000 Subject: [PATCH 5/5] decouple lyric area from AudioPlayer component and change the structure of files to make it easy to breakdown into seperate modules --- scripts/content.json => content/index.json | 0 {content/images => images}/bg.jpg | Bin index.html | 2 +- scripts/selected.js | 220 +++++++++++---------- styles/style.css | 2 +- 5 files changed, 118 insertions(+), 106 deletions(-) rename scripts/content.json => content/index.json (100%) rename {content/images => images}/bg.jpg (100%) 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 f137816..d162d13 100644 --- a/scripts/selected.js +++ b/scripts/selected.js @@ -6,32 +6,31 @@ * 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(); } + function Selected() { - this.audioPlayer = new AudioPlayer(document.getElementById('audio'), document.getElementById('lyricContainer')) - this.playlist = new PlayList(document.getElementById('playlist'), this); - this.lyricStyle = 0; //random num to specify the different class name for lyric + 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() }, - getLyricContainer: function(){ - return this.audioPlayer.getLyricContainer() - }, init: function() { - var that = this; - //get all songs and add to the playlist - this.playlist.init('./scripts/content.json'); - this.audioPlayer.init(); - window.addEventListener("playnext", function(){ - that.playNext(that); - }) - + 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) { @@ -42,7 +41,6 @@ Selected.prototype = { } } }, false); - //initialize the background setting document.getElementById('bg_dark').addEventListener('click', function() { document.getElementsByTagName('html')[0].className = 'colorBg'; @@ -50,68 +48,64 @@ Selected.prototype = { document.getElementById('bg_pic').addEventListener('click', function() { document.getElementsByTagName('html')[0].className = 'imageBg'; }); - - }, - play: function(songName) { - var that = this; - this.audioPlayer.reset() - this.lyricStyle = Math.floor(Math.random() * 4); - this.audioPlayer.play('./content/songs/' + songName + '.mp3', './content/songs/' + songName + '.lrc') - //sync the lyric - this.getAudio().addEventListener("timeupdate", function(e) { - if (!that.audioPlayer.getLyricText()) return; - for (var i = 0, l = that.audioPlayer.getLyricText().length; i < l; i++) { - if (this.currentTime > that.audioPlayer.getLyricText()[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.getLyricContainer().style.top = 130 - line.offsetTop + 'px'; - }; - }; - }); - }, - playNext: function(that) { - this.playlist.moveToNext(); - this.playlist.setClass(); - var songName = this.playlist.getCurrentSong().children[0].getAttribute('data-name'); - window.location.hash = songName; - that.play(songName); } -}; +} function AudioPlayer(audioContainer, lyricContainer) { this.audioContainer = audioContainer - this.lyricContainer = lyricContainer - this.lyric = null; + } AudioPlayer.prototype = { - init: function(){ + init: function(onTimeUpdate, onPlayerError){ + var that = this this.audioContainer.onended = function() { - window.dispatchEvent(new Event("playnext")) - } + window.dispatchEvent(new Event("audioFinished")) + }; this.audioContainer.onerror = function(e) { - this.lyricContainer.textContent = '!fail to load the song :('; + onPlayerError(e); }; + this.audioContainer.addEventListener("timeupdate", function(e) { + onTimeUpdate(e, that.audioContainer.currentTime) + }); + window.addEventListener("playAudio", function(e){ + that.play('./content/songs/' + e.audioName + '.mp3'); + }); + }, + getAudioContainer: function () { + return this.audioContainer + }, + play: function(mediaUrl) { + this.getAudioContainer().addEventListener('canplay', function() { + this.play(); + }); + this.getAudioContainer().src = mediaUrl; + } +} + +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; }, - reset: function(){ + init: function(){ + var that = this; + 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.lyricContainer.textContent = 'loading...'; + this.getSubtitleContainer().style.top = '130px'; + this.getSubtitleContainer().textContent = 'loading...'; //empty the lyric this.setLyricText(null) - }, - getAudioContainer: function () { - return this.audioContainer - }, - getLyricContainer: function () { - return this.lyricContainer + this.lyricStyle = Math.floor(Math.random() * 4); }, getLyricText: function(){ return this.lyric @@ -119,37 +113,61 @@ AudioPlayer.prototype = { setLyricText: function(lyric){ this.lyric = lyric }, - addEventListener: function(event){ - var audioPlayer = this - this.getAudioContainer().addEventListener(event.eventName, function() { - audioPlayer.loadLyric('./content/songs/' + event.songName + '.lrc'); - this.play(); - }); + getPlayerErrorHandler: function(){ + var that = this; + return function(e){ + that.getSubtitleContainer().textContent = '!fail to load the song :('; + } }, - play: function(mediaUrl, lyricUrl) { - var audioPlayer = this - this.getAudioContainer().addEventListener('canplay', function() { - audioPlayer.loadLyric(lyricUrl); - this.play(); + 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'; + }; + }; + }, + 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); }); - this.getAudioContainer().src = mediaUrl + lyricContainer.appendChild(fragment); }, loadLyric: function(url) { - var audioPlayer = this, + var that = this, request = new XMLHttpRequest(); request.open('GET', url, true); request.responseType = 'text'; //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() { - audioPlayer.setLyricText(audioPlayer.parseLyric(request.response)); + that.setLyricText(that.parseLyric(request.response)); //display lyric to the page - audioPlayer.appendLyric(audioPlayer.getLyricText()); + that.appendLyric(that.getLyricText()); }; request.onerror = request.onabort = function(e) { - audioPlayer.getLyricContainer().textContent = '!failed to load the lyric :('; - } - this.getLyricContainer().textContent = 'loading lyric...'; + that.getSubtitleContainer().textContent = '!failed to load the lyric :('; + }; + that.reset(); request.send(); }, parseLyric: function(text) { @@ -158,7 +176,6 @@ AudioPlayer.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); @@ -184,19 +201,6 @@ AudioPlayer.prototype = { }); return result; }, - appendLyric: function(lyric) { - var lyricContainer = this.getLyricContainer(), - 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); - }, getOffset: function(text) { //Returns offset in miliseconds. var offset = 0; @@ -213,12 +217,11 @@ AudioPlayer.prototype = { offset = 0; } return offset; - } + }, } -function PlayList(playListContainer, player){ +function PlayList(playListContainer){ this.container = playListContainer; - this.player = player this.currentIndex=0 } @@ -251,12 +254,22 @@ PlayList.prototype = { //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() { @@ -270,18 +283,17 @@ PlayList.prototype = { 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() - this.player.play(this.getCurrentSong().children[0].getAttribute('data-name')); + var playAudioEvent = new Event("playAudio"); + playAudioEvent.audioName = this.getCurrentSong().children[0].getAttribute('data-name'); + window.dispatchEvent(playAudioEvent) }, refreshPlayList: function(data) { var playList = this, @@ -314,7 +326,9 @@ PlayList.prototype = { playList.setClass(); var songName = e.target.getAttribute('data-name'); window.location.hash = songName; - playList.player.play(songName); + var playAudioEvent = new Event("playAudio"); + playAudioEvent.audioName = songName; + window.dispatchEvent(playAudioEvent) }, false); }, getSongIndex: function(songName) { @@ -328,5 +342,3 @@ PlayList.prototype = { 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;