HTML5 Music Player (本地拖曳撥放)

代碼起源

tommy351 提供的。基於 HTML5 和 JQuery 運行的撥放器,HTML5 支持 .mp3, .wav, .ogg 這三個種音樂格式的撥放。

HTML5 <audio> 支持音樂撥放,撥放種類有限,而且 HTML5 不知道穩定了沒。

  • 本次挑戰的項目
    • 增加 拖曳檔案 的撥放
    • 拖曳檔案 和 拖曳資料夾 的摸索
    • HTML5 圍觀

修改

demo
如上圖,最後一個檔案 mo - 45. not yet.mp3 是拖曳上傳的內容。

歷程

  • 增加本地拖曳撥放,操作方式為將本地 mp3 檔案上傳

檔案能知道的資訊有限

  • name 取得檔案名稱,如果需要做副檔名檢查,可利用它。
  • size 取得檔案大小 (bytes)。
  • type 取得檔案的 MIME 型別 (若無法對應會傳回空白)。

因為安全性,所以得不到路徑資訊,也就是不能隨便去掃描別人的本地資料。因此要完成資料夾下的所有檔案獲取會有困難。

其實瀏覽器本上就會支持檔案拖曳,並且在新分頁上顯示資訊,如果要避免瀏覽器自己開啟檔案,則使用 evt.stopPropagation() 將 drag 相關事件關閉,也就是說在當前頁面的 drag 事件都不會被瀏覽器知曉。

後記

  • 對於 .mp3 檔案要自動獲得專輯封面可能嗎?
    請參考 這裡

    1
    2
    3
    4
    5
    6
    7
    8
    Field Length Offsets
    Tag 3 0-2
    Songname 30 3-32
    Artist 30 33-62
    Album 30 63-92
    Year 4 93-96
    Comment 30 97-126
    Genre 1 127

    要自己擠去 parsing 這些資訊還是算了吧。WRYYYYY

  • 對於資料夾整包拖曳操作?HTML5 drag upload folder
    在寫這篇的時候,網路上有 demo,但是只有在 chrome 21 下才有支持,看起來是從瀏覽器 ( 本地軟件 ) 來協助遍歷資料夾下的內容。不然是沒有權限去運行的。

  • 讀取的異步操作 ?
    是的,最近在編寫時常會遇到異步操作,也就是必須全用 callback 來完成,因為讀檔的資訊完成的 callback function 中並沒有 file.name 之類的資訊,如果適用 for(var i in files) 運行,會發生變數找不到的情況,因此用 $.each()reader.onload() 來完成這個操作。

    • onloadstart、onprogress、onload、onabort、onerror 和 onloadend 這幾個可以協助監控上傳資訊,也就是能知曉現在上傳進度 … 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
var dropbox = document.getElementById("dropbox")
// init event handlers
dropbox.addEventListener("dragenter", dragEnter, false);
dropbox.addEventListener("dragexit", dragExit, false);
dropbox.addEventListener("dragover", dragOver, false);
dropbox.addEventListener("drop", drop, false);
// init the widgets
$("#progressbar").progressbar();
function dragEnter(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragExit(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function drop(evt) {
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files;
var count = files.length;
// Only call the handler if 1 or more files was dropped.
if (count > 0)
handleFiles(files);
}
function handleFiles(files) {
var mp3Files = $.map(files, function(f, i) {
return f.type.indexOf("audio/mp3") == 0 ? f : null;
});
$.each(mp3Files, function(i, file) {
document.getElementById("droplabel").innerHTML = "Processing " + file.name;
var reader = new FileReader();
reader.onload = function(evt) {
$('#playlist').append('<li>' + 'mo' + ' - ' + file.name + '</li>');
playlist.push({
title: file.name,
mp3: evt.target.result
});
$('#playlist li').each(function(i) {
var _i = i;
$(this).on('click', function() {
switchTrack(_i);
});
});
}
// init the reader event handlers
reader.onprogress = handleReaderProgress;
reader.onloadend = handleReaderLoadEnd;
reader.readAsDataURL(file);
});
}
function handleReaderProgress(evt) {
if (evt.lengthComputable) {
var loaded = (evt.loaded / evt.total);
$("#progressbar").progressbar({
value: loaded * 100
});
}
}
function handleReaderLoadEnd(evt) {
$("#progressbar").progressbar({
value: 100
});
}

最後

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>Cover Art</title>
<link rel="stylesheet" href="css/stylesheets/style.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
</head>
<body>
<div id="playblock">
<div id="player">
<div class="cover"></div>
<div class="ctrl">
<div class="tag">
<strong>Title</strong>
<span class="artist">Artist</span>
<span class="album">Album</span>
</div>
<div class="control">
<div class="left">
<div class="rewind icon"></div>
<div class="playback icon"></div>
<div class="fastforward icon"></div>
</div>
<div class="volume right">
<div class="mute icon left"></div>
<div class="slider left">
<div class="pace"></div>
</div>
</div>
</div>
<div class="progress">
<div class="slider">
<div class="loaded"></div>
<div class="pace"></div>
</div>
<div class="timer left">0:00</div>
<div class="right">
<div class="repeat icon"></div>
<div class="shuffle icon"></div>
</div>
</div>
</div>
</div>
<div id="dropbox">
<div id="dropctrl">
<span id="droplabel">Drop mp3 file here...</span>
<div id="progressbar"></div>
</div>
<ul id="playlist"></ul>
</div>
</div>
<footer>
Copyright &copy; 2012 <a href="http://zespia.tw">SkyArrow</a>
</footer>
<script src="js/script.js"></script>
</body>
</html>

js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
(function($) {
// Settings
var repeat = localStorage.repeat || 0,
shuffle = localStorage.shuffle || 'false',
continous = true,
autoplay = false,
playlist = [{
title: 'ふわふわ時間',
artist: '桜高軽音部(平沢唯、秋山澪、田井中律、琴吹紬)',
album: 'TVアニメ「けいおん!」劇中歌 - ふわふわ時間',
cover: 'http://dl.dropbox.com/u/5480889/coverart/cover/ふわふわ時間.jpg',
mp3: 'http://dl.dropbox.com/u/5480889/coverart/music/ふわふわ時間.mp3',
ogg: 'http://dl.dropbox.com/u/5480889/coverart/music/ふわふわ時間.ogg'
}, {
title: 'リップシンク',
artist: 'nano.RIPE',
album: '細胞キオク',
cover: 'http://dl.dropbox.com/u/5480889/coverart/cover/リップシンク.jpg',
mp3: 'http://dl.dropbox.com/u/5480889/coverart/music/リップシンク.mp3',
ogg: 'http://dl.dropbox.com/u/5480889/coverart/music/リップシンク.ogg'
}];
for (var i = 0; i < playlist.length; i++) {
var item = playlist[i];
$('#playlist').append('<li>' + item.artist + ' - ' + item.title + '</li>');
}
// Load playlist
var time = new Date(),
currentTrack = shuffle === 'true' ? time.getTime() % playlist.length : 0,
trigger = false,
audio, timeout, isPlaying, playCounts;
var play = function() {
audio.play();
$('.playback').addClass('playing');
timeout = setInterval(updateProgress, 500);
isPlaying = true;
}
var pause = function() {
audio.pause();
$('.playback').removeClass('playing');
clearInterval(updateProgress);
isPlaying = false;
}
// Update progress
var setProgress = function(value) {
var currentSec = parseInt(value % 60) < 10 ? '0' + parseInt(value % 60) : parseInt(value % 60),
ratio = value / audio.duration * 100;
$('.timer').html(parseInt(value / 60) + ':' + currentSec);
$('.progress .pace').css('width', ratio + '%');
$('.progress .slider a').css('left', ratio + '%');
}
var updateProgress = function() {
setProgress(audio.currentTime);
}
// Progress slider
$('.progress .slider').slider({
step: 0.1,
slide: function(event, ui) {
$(this).addClass('enable');
setProgress(audio.duration * ui.value / 100);
clearInterval(timeout);
},
stop: function(event, ui) {
audio.currentTime = audio.duration * ui.value / 100;
$(this).removeClass('enable');
timeout = setInterval(updateProgress, 500);
}
});
// Volume slider
var setVolume = function(value) {
audio.volume = localStorage.volume = value;
$('.volume .pace').css('width', value * 100 + '%');
$('.volume .slider a').css('left', value * 100 + '%');
}
var volume = localStorage.volume || 0.5;
$('.volume .slider').slider({
max: 1,
min: 0,
step: 0.01,
value: volume,
slide: function(event, ui) {
setVolume(ui.value);
$(this).addClass('enable');
$('.mute').removeClass('enable');
},
stop: function() {
$(this).removeClass('enable');
}
}).children('.pace').css('width', volume * 100 + '%');
$('.mute').click(function() {
if ($(this).hasClass('enable')) {
setVolume($(this).data('volume'));
$(this).removeClass('enable');
} else {
$(this).data('volume', audio.volume).addClass('enable');
setVolume(0);
}
});
// Switch track
var switchTrack = function(i) {
if (i < 0) {
track = currentTrack = playlist.length - 1;
} else if (i >= playlist.length) {
track = currentTrack = 0;
} else {
track = i;
}
$('audio').remove();
loadMusic(track);
if (isPlaying == true) play();
}
// Shuffle
var shufflePlay = function() {
var time = new Date(),
lastTrack = currentTrack;
currentTrack = time.getTime() % playlist.length;
if (lastTrack == currentTrack)++currentTrack;
switchTrack(currentTrack);
}
// Fire when track ended
var ended = function() {
pause();
audio.currentTime = 0;
playCounts++;
if (continous == true) isPlaying = true;
if (repeat == 1) {
play();
} else {
if (shuffle === 'true') {
shufflePlay();
} else {
if (repeat == 2) {
switchTrack(++currentTrack);
} else {
if (currentTrack < playlist.length) switchTrack(++currentTrack);
}
}
}
}
var beforeLoad = function() {
var endVal = this.seekable && this.seekable.length ? this.seekable.end(0) : 0;
$('.progress .loaded').css('width', (100 / (this.duration || 1) * endVal) + '%');
}
// Fire when track loaded completely
var afterLoad = function() {
if (autoplay == true) play();
}
// Load track
var loadMusic = function(i) {
var item = playlist[i],
newaudio = $('<audio>').html('<source src="' + item.mp3 + '"><source src="' + item.ogg + '">').appendTo('#player');
$('.cover').html('<img src="' + item.cover + '" alt="' + item.album + '">');
$('.tag').html('<strong>' + item.title + '</strong><span class="artist">' + item.artist + '</span><span class="album">' + item.album + '</span>');
$('#playlist li').removeClass('playing').eq(i).addClass('playing');
audio = newaudio[0];
audio.volume = $('.mute').hasClass('enable') ? 0 : volume;
audio.addEventListener('progress', beforeLoad, false);
audio.addEventListener('durationchange', beforeLoad, false);
audio.addEventListener('canplay', afterLoad, false);
audio.addEventListener('ended', ended, false);
}
loadMusic(currentTrack);
$('.playback').on('click', function() {
if ($(this).hasClass('playing')) {
pause();
} else {
play();
}
});
$('.rewind').on('click', function() {
if (shuffle === 'true') {
shufflePlay();
} else {
switchTrack(--currentTrack);
}
});
$('.fastforward').on('click', function() {
if (shuffle === 'true') {
shufflePlay();
} else {
switchTrack(++currentTrack);
}
});
$('#playlist li').each(function(i) {
var _i = i;
$(this).on('click', function() {
switchTrack(_i);
});
});
if (shuffle === 'true') $('.shuffle').addClass('enable');
if (repeat == 1) {
$('.repeat').addClass('once');
} else if (repeat == 2) {
$('.repeat').addClass('all');
}
$('.repeat').on('click', function() {
if ($(this).hasClass('once')) {
repeat = localStorage.repeat = 2;
$(this).removeClass('once').addClass('all');
} else if ($(this).hasClass('all')) {
repeat = localStorage.repeat = 0;
$(this).removeClass('all');
} else {
repeat = localStorage.repeat = 1;
$(this).addClass('once');
}
});
$('.shuffle').on('click', function() {
if ($(this).hasClass('enable')) {
shuffle = localStorage.shuffle = 'false';
$(this).removeClass('enable');
} else {
shuffle = localStorage.shuffle = 'true';
$(this).addClass('enable');
}
});
var dropbox = document.getElementById("dropbox")
// init event handlers
dropbox.addEventListener("dragenter", dragEnter, false);
dropbox.addEventListener("dragexit", dragExit, false);
dropbox.addEventListener("dragover", dragOver, false);
dropbox.addEventListener("drop", drop, false);
// init the widgets
$("#progressbar").progressbar();
function dragEnter(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragExit(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function drop(evt) {
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files;
var count = files.length;
// Only call the handler if 1 or more files was dropped.
if (count > 0)
handleFiles(files);
}
function handleFiles(files) {
var mp3Files = $.map(files, function(f, i) {
return f.type.indexOf("audio/mp3") == 0 ? f : null;
});
$.each(mp3Files, function(i, file) {
document.getElementById("droplabel").innerHTML = "Processing " + file.name;
var reader = new FileReader();
reader.onload = function(evt) {
$('#playlist').append('<li>' + 'mo' + ' - ' + file.name + '</li>');
playlist.push({
title: file.name,
mp3: evt.target.result
});
$('#playlist li').each(function(i) {
var _i = i;
$(this).on('click', function() {
switchTrack(_i);
});
});
}
// init the reader event handlers
reader.onprogress = handleReaderProgress;
reader.onloadend = handleReaderLoadEnd;
reader.readAsDataURL(file);
});
}
function handleReaderProgress(evt) {
if (evt.lengthComputable) {
var loaded = (evt.loaded / evt.total);
$("#progressbar").progressbar({
value: loaded * 100
});
}
}
function handleReaderLoadEnd(evt) {
$("#progressbar").progressbar({
value: 100
});
}
})(jQuery);
Read More +