hexo-theme-shokax 0.3.13 → 0.4.0-alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/_config.yml +16 -9
  2. package/layout/_mixin/comment.pug +4 -4
  3. package/layout/_partials/footer.pug +1 -1
  4. package/layout/_partials/head/head.pug +7 -1
  5. package/layout/_partials/header.pug +1 -1
  6. package/layout/_partials/layout.pug +0 -10
  7. package/layout/_partials/post/footer.pug +1 -2
  8. package/package.json +10 -5
  9. package/scripts/generaters/archive.js +1 -1
  10. package/scripts/generaters/script.js +64 -42
  11. package/scripts/helpers/asset.js +1 -1
  12. package/scripts/helpers/list_categories.js +0 -4
  13. package/scripts/plugin/lib/injects.js +1 -1
  14. package/source/js/_app/components/comments.ts +33 -0
  15. package/source/js/_app/components/sidebar.ts +239 -0
  16. package/source/js/_app/globals/globalVars.ts +99 -0
  17. package/source/js/_app/globals/handles.ts +123 -0
  18. package/source/js/_app/globals/themeColor.ts +64 -0
  19. package/source/js/_app/globals/thirdparty.ts +63 -0
  20. package/source/js/_app/globals/tools.ts +75 -0
  21. package/source/js/_app/library/anime.ts +110 -0
  22. package/source/js/_app/library/declare.d.ts +126 -0
  23. package/source/js/_app/library/dom.ts +26 -0
  24. package/source/js/_app/library/loadFile.ts +43 -0
  25. package/source/js/_app/library/proto.ts +141 -0
  26. package/source/js/_app/library/scriptPjax.ts +72 -0
  27. package/source/js/_app/library/storage.ts +12 -0
  28. package/source/js/_app/library/vue.ts +50 -0
  29. package/source/js/_app/page/common.ts +42 -0
  30. package/source/js/_app/page/fancybox.ts +71 -0
  31. package/source/js/_app/page/post.ts +266 -0
  32. package/source/js/_app/page/search.ts +135 -0
  33. package/source/js/_app/page/tab.ts +60 -0
  34. package/source/js/_app/pjax/domInit.ts +96 -0
  35. package/source/js/_app/pjax/refresh.ts +78 -0
  36. package/source/js/_app/pjax/siteInit.ts +72 -0
  37. package/source/js/_app/player.ts +799 -0
  38. package/source/assets/beian.webp +0 -0
  39. package/source/js/_app/components/sidebar.js +0 -209
  40. package/source/js/_app/fireworks.js +0 -10
  41. package/source/js/_app/globals/globalVars.js +0 -80
  42. package/source/js/_app/globals/handles.js +0 -98
  43. package/source/js/_app/globals/themeColor.js +0 -62
  44. package/source/js/_app/globals/thirdparty.js +0 -62
  45. package/source/js/_app/globals/tools.js +0 -66
  46. package/source/js/_app/library/anime.js +0 -106
  47. package/source/js/_app/library/dom.js +0 -34
  48. package/source/js/_app/library/loadFile.js +0 -36
  49. package/source/js/_app/library/proto.js +0 -163
  50. package/source/js/_app/library/scriptPjax.js +0 -70
  51. package/source/js/_app/library/storage.js +0 -12
  52. package/source/js/_app/library/vue.js +0 -53
  53. package/source/js/_app/page/comment.js +0 -23
  54. package/source/js/_app/page/common.js +0 -41
  55. package/source/js/_app/page/fancybox.js +0 -65
  56. package/source/js/_app/page/post.js +0 -244
  57. package/source/js/_app/page/search.js +0 -118
  58. package/source/js/_app/page/tab.js +0 -53
  59. package/source/js/_app/pjax/domInit.js +0 -76
  60. package/source/js/_app/pjax/refresh.js +0 -52
  61. package/source/js/_app/pjax/siteInit.js +0 -51
  62. package/source/js/_app/player.js +0 -771
@@ -0,0 +1,799 @@
1
+ import { CONFIG, originTitle } from './globals/globalVars'
2
+ import { showtip } from './globals/tools'
3
+ import { pageScroll } from './library/anime'
4
+ import { $dom } from './library/dom'
5
+ import { $storage } from './library/storage'
6
+ import { tabFormat } from './page/tab'
7
+ import { createChild, getLeft, getWidth, setDisplay, setWidth } from './library/proto'
8
+
9
+ let NOWPLAYING = null
10
+
11
+ const isMobile = /mobile/i.test(window.navigator.userAgent)
12
+ export const mediaPlayer = (t, config?) => {
13
+ const buttons = {
14
+ el: {},
15
+ create () {
16
+ if (!t.player.options.btns) { return }
17
+ t.player.options.btns.forEach((item) => {
18
+ if (buttons.el[item]) { return }
19
+
20
+ buttons.el[item] = createChild(t, 'div', {
21
+ className: item + ' btn',
22
+ onclick (event) {
23
+ t.player.fetch().then(() => {
24
+ t.player.options.events[item](event)
25
+ })
26
+ }
27
+ })
28
+ })
29
+ }
30
+ }
31
+ const controller = {
32
+ el: null,
33
+ btns: {
34
+ mode: undefined,
35
+ volume: undefined
36
+ },
37
+ step: 'next',
38
+ create: () => {
39
+ if (!t.player.options.controls) { return }
40
+
41
+ const that = controller
42
+ t.player.options.controls.forEach((item) => {
43
+ if (that.btns[item]) { return }
44
+
45
+ const opt = <HTMLElement> {
46
+ onclick (event) {
47
+ that.events[item] ? that.events[item](event) : t.player.options.events[item](event)
48
+ }
49
+ }
50
+
51
+ switch (item) {
52
+ case 'volume':
53
+ opt.className = ' ' + (source.muted ? 'off' : 'on')
54
+ opt.innerHTML = '<div class="bar"></div>'
55
+ opt['on' + utils.nameMap.dragStart] = that.events.volume
56
+ opt.onclick = null
57
+ break
58
+ case 'mode':
59
+ opt.className = ' ' + t.player.options.mode
60
+ break
61
+ default:
62
+ opt.className = ''
63
+ break
64
+ }
65
+
66
+ opt.className = item + opt.className + ' btn'
67
+
68
+ that.btns[item] = createChild(that.el, 'div', opt)
69
+ })
70
+
71
+ that.btns.volume.bar = that.btns.volume.child('.bar')
72
+ },
73
+ events: {
74
+ mode (e) {
75
+ switch (t.player.options.mode) {
76
+ case 'loop':
77
+ t.player.options.mode = 'random'
78
+ break
79
+ case 'random':
80
+ t.player.options.mode = 'order'
81
+ break
82
+ default:
83
+ t.player.options.mode = 'loop'
84
+ }
85
+
86
+ controller.btns.mode.className = 'mode ' + t.player.options.mode + ' btn'
87
+ $storage.set('_PlayerMode', t.player.options.mode)
88
+ },
89
+ volume (e) {
90
+ e.preventDefault()
91
+
92
+ const current = e.currentTarget
93
+
94
+ let drag = false
95
+
96
+ const thumbMove = (e) => {
97
+ e.preventDefault()
98
+ t.player.volume(controller.percent(e, current))
99
+ drag = true
100
+ }
101
+
102
+ const thumbUp = (e) => {
103
+ e.preventDefault()
104
+ current.removeEventListener(utils.nameMap.dragEnd, thumbUp)
105
+ current.removeEventListener(utils.nameMap.dragMove, thumbMove)
106
+ if (drag) {
107
+ t.player.muted()
108
+ t.player.volume(controller.percent(e, current))
109
+ } else {
110
+ if (source.muted) {
111
+ t.player.muted()
112
+ t.player.volume(source.volume)
113
+ } else {
114
+ t.player.muted('muted')
115
+ controller.update(0)
116
+ }
117
+ }
118
+ }
119
+
120
+ current.addEventListener(utils.nameMap.dragMove, thumbMove)
121
+ current.addEventListener(utils.nameMap.dragEnd, thumbUp)
122
+ },
123
+ backward (e) {
124
+ controller.step = 'prev'
125
+ t.player.mode()
126
+ },
127
+ forward (e) {
128
+ controller.step = 'next'
129
+ t.player.mode()
130
+ }
131
+ },
132
+ update (percent) {
133
+ controller.btns.volume.className = 'volume ' + (!source.muted && percent > 0 ? 'on' : 'off') + ' btn'
134
+ setWidth(controller.btns.volume.bar, Math.floor(percent * 100) + '%')
135
+ },
136
+ percent (e, el) {
137
+ let percentage = ((e.clientX || e.changedTouches[0].clientX) - getLeft(el)) / getWidth(el)
138
+ percentage = Math.max(percentage, 0)
139
+ return Math.min(percentage, 1)
140
+ }
141
+ }
142
+ const progress = {
143
+ el: null,
144
+ bar: null,
145
+ create () {
146
+ const current = playlist.current().el
147
+
148
+ if (current) {
149
+ if (progress.el) {
150
+ progress.el.parentNode.removeClass('current')
151
+ .removeEventListener(utils.nameMap.dragStart, progress.drag)
152
+ progress.el.remove()
153
+ }
154
+
155
+ progress.el = createChild(current, 'div', {
156
+ className: 'progress'
157
+ });
158
+
159
+ (progress.el as HTMLElement).setAttribute('data-dtime', utils.secondToTime(0))
160
+
161
+ progress.bar = createChild(progress.el, 'div', {
162
+ className: 'bar'
163
+ })
164
+
165
+ current.addClass('current')
166
+
167
+ current.addEventListener(utils.nameMap.dragStart, progress.drag)
168
+
169
+ playlist.scroll()
170
+ }
171
+ },
172
+ update (percent) {
173
+ setWidth(progress.bar, Math.floor(percent * 100) + '%');
174
+ (progress.el as HTMLElement).setAttribute('data-ptime', utils.secondToTime(percent * source.duration))
175
+ },
176
+ seeking (type) {
177
+ if (type) { progress.el.addClass('seeking') } else { progress.el.removeClass('seeking') }
178
+ },
179
+ percent (e, el) {
180
+ let percentage = ((e.clientX || e.changedTouches[0].clientX) - getLeft(el)) / getWidth(el)
181
+ percentage = Math.max(percentage, 0)
182
+ return Math.min(percentage, 1)
183
+ },
184
+ drag (e) {
185
+ e.preventDefault()
186
+
187
+ const current = playlist.current().el
188
+
189
+ const thumbMove = (e) => {
190
+ e.preventDefault()
191
+ const percentage = progress.percent(e, current)
192
+ progress.update(percentage)
193
+ lyrics.update(percentage * source.duration)
194
+ }
195
+
196
+ const thumbUp = (e) => {
197
+ e.preventDefault()
198
+ current.removeEventListener(utils.nameMap.dragEnd, thumbUp)
199
+ current.removeEventListener(utils.nameMap.dragMove, thumbMove)
200
+ const percentage = progress.percent(e, current)
201
+ progress.update(percentage)
202
+ t.player.seek(percentage * source.duration)
203
+ source.disableTimeupdate = false
204
+ progress.seeking(false)
205
+ }
206
+
207
+ source.disableTimeupdate = true
208
+ progress.seeking(true)
209
+ current.addEventListener(utils.nameMap.dragMove, thumbMove)
210
+ current.addEventListener(utils.nameMap.dragEnd, thumbUp)
211
+ }
212
+ }
213
+ const preview = {
214
+ el: null,
215
+ create () {
216
+ const current = playlist.current()
217
+
218
+ preview.el.innerHTML = '<div class="cover"><div class="disc"><img src="' + (current.cover) + '" class="blur" alt="music cover"/></div></div>' +
219
+ '<div class="info"><h4 class="title">' + current.name + '</h4><span>' + current.artist + '</span>' +
220
+ '<div class="lrc"></div></div>'
221
+
222
+ preview.el.child('.cover').addEventListener('click', t.player.options.events['play-pause'])
223
+
224
+ lyrics.create(preview.el.child('.lrc'))
225
+ }
226
+ }
227
+ let source
228
+ const playlist = {
229
+ el: null,
230
+ data: [],
231
+ index: -1,
232
+ errnum: 0,
233
+ add: (group, list) => {
234
+ list.forEach((item) => {
235
+ item.group = group
236
+ item.name = item.name || item.title || 'Meida name'
237
+ item.artist = item.artist || item.author || 'Anonymous'
238
+ item.cover = item.cover || item.pic
239
+ item.type = item.type || 'normal'
240
+
241
+ playlist.data.push(item)
242
+ })
243
+ },
244
+ clear () {
245
+ playlist.data = []
246
+ playlist.el.innerHTML = ''
247
+
248
+ if (playlist.index !== -1) {
249
+ playlist.index = -1
250
+ t.player.fetch()
251
+ }
252
+ },
253
+ create () {
254
+ const el = playlist.el
255
+
256
+ playlist.data.map((item, index) => {
257
+ if (item.el) { return null }
258
+
259
+ const id = 'list-' + t.player._id + '-' + item.group
260
+ let tab = $dom('#' + id)
261
+ if (!tab) {
262
+ tab = createChild(el, 'div', {
263
+ id,
264
+ className: t.player.group ? 'tab' : '',
265
+ innerHTML: '<ol></ol>'
266
+ })
267
+ if (t.player.group) {
268
+ tab.setAttribute('data-title', t.player.options.rawList[item.group].title)
269
+ tab.setAttribute('data-id', t.player._id)
270
+ }
271
+ }
272
+
273
+ item.el = createChild(tab.child('ol'), 'li', {
274
+ title: item.name + ' - ' + item.artist,
275
+ innerHTML: '<span class="info"><span>' + item.name + '</span><span>' + item.artist + '</span></span>',
276
+ onclick (event) {
277
+ const current = event.currentTarget
278
+ if (playlist.index === index && progress.el) {
279
+ if (source.paused) {
280
+ t.player.play()
281
+ } else {
282
+ t.player.seek(source.duration * progress.percent(event, current))
283
+ }
284
+ return
285
+ }
286
+ t.player.switch(index)
287
+ t.player.play()
288
+ }
289
+ })
290
+
291
+ return item
292
+ })
293
+ if (__shokax_tabs__) {
294
+ tabFormat()
295
+ }
296
+ },
297
+ current () {
298
+ return this.data[this.index]
299
+ },
300
+ scroll () {
301
+ const item = this.current()
302
+ let li = this.el.child('li.active')
303
+ li && li.removeClass('active')
304
+ let tab = this.el.child('.tab.active')
305
+ tab && tab.removeClass('active')
306
+ li = this.el.find('.nav li')[item.group]
307
+ li && li.addClass('active')
308
+ tab = this.el.find('.tab')[item.group]
309
+ tab && tab.addClass('active')
310
+
311
+ pageScroll(item.el, item.el.offsetTop)
312
+
313
+ return this
314
+ },
315
+ title () {
316
+ if (source.paused) { return }
317
+
318
+ const current = this.current()
319
+ document.title = 'Now Playing...' + current.name + ' - ' + current.artist + ' | ' + originTitle
320
+ },
321
+ error () {
322
+ const current = this.current()
323
+ current.el.removeClass('current').addClass('error')
324
+ current.error = true
325
+ this.errnum++
326
+ }
327
+ }
328
+ const info = {
329
+ el: null,
330
+ create () {
331
+ if (this.el) { return }
332
+
333
+ this.el = createChild(t, 'div', {
334
+ className: 'player-info',
335
+ innerHTML: (t.player.options.type === 'audio' ? '<div class="preview"></div>' : '') + '<div class="controller"></div><div class="playlist"></div>'
336
+ }, 'after')
337
+
338
+ preview.el = this.el.child('.preview')
339
+ playlist.el = this.el.child('.playlist')
340
+ controller.el = this.el.child('.controller')
341
+ },
342
+ hide () {
343
+ const el = this.el
344
+ el.addClass('hide')
345
+ window.setTimeout(() => {
346
+ el.removeClass('show hide')
347
+ }, 300)
348
+ }
349
+ }
350
+ const option = {
351
+ type: 'audio',
352
+ mode: 'random',
353
+ btns: ['play-pause', 'music'],
354
+ controls: ['mode', 'backward', 'play-pause', 'forward', 'volume'],
355
+ events: {
356
+ 'play-pause' (event) {
357
+ if (source.paused) {
358
+ t.player.play()
359
+ } else {
360
+ t.player.pause()
361
+ }
362
+ },
363
+ music (event) {
364
+ if (info.el.hasClass('show')) {
365
+ info.hide()
366
+ } else {
367
+ info.el.addClass('show')
368
+ playlist.scroll().title()
369
+ }
370
+ }
371
+ }
372
+ }
373
+ const utils = {
374
+ random (len) {
375
+ return Math.floor((Math.random() * len))
376
+ },
377
+ parse (link) {
378
+ let result = [];
379
+ [
380
+ ['music.163.com.*song.*id=(\\d+)', 'netease', 'song'],
381
+ ['music.163.com.*album.*id=(\\d+)', 'netease', 'album'],
382
+ ['music.163.com.*artist.*id=(\\d+)', 'netease', 'artist'],
383
+ ['music.163.com.*playlist.*id=(\\d+)', 'netease', 'playlist'],
384
+ ['music.163.com.*discover/toplist.*id=(\\d+)', 'netease', 'playlist'],
385
+ ['y.qq.com.*song/(\\w+)(.html)?', 'tencent', 'song'],
386
+ ['y.qq.com.*album/(\\w+)(.html)?', 'tencent', 'album'],
387
+ ['y.qq.com.*singer/(\\w+)(.html)?', 'tencent', 'artist'],
388
+ ['y.qq.com.*playsquare/(\\w+)(.html)?', 'tencent', 'playlist'],
389
+ ['y.qq.com.*playlist/(\\w+)(.html)?', 'tencent', 'playlist'],
390
+ ['xiami.com.*song/(\\w+)', 'xiami', 'song'],
391
+ ['xiami.com.*album/(\\w+)', 'xiami', 'album'],
392
+ ['xiami.com.*artist/(\\w+)', 'xiami', 'artist'],
393
+ ['xiami.com.*collect/(\\w+)', 'xiami', 'playlist']
394
+ ].forEach((rule) => {
395
+ const patt = new RegExp(rule[0])
396
+ const res = patt.exec(link)
397
+ if (res !== null) {
398
+ result = [rule[1], rule[2], res[1]]
399
+ }
400
+ })
401
+ return result
402
+ },
403
+ fetch (source) {
404
+ const list = []
405
+
406
+ return new Promise((resolve, reject) => {
407
+ source.forEach((raw) => {
408
+ const meta = utils.parse(raw)
409
+ if (meta[0]) {
410
+ const skey = JSON.stringify(meta)
411
+ const playlist = $storage.get(skey)
412
+ if (playlist) {
413
+ // list.push.apply(list, JSON.parse(playlist))
414
+ list.push(...JSON.parse(playlist))
415
+ resolve(list)
416
+ } else {
417
+ fetch(`${CONFIG.playerAPI}/meting/?server=` + meta[0] + '&type=' + meta[1] + '&id=' + meta[2] + '&r=' + Math.random())
418
+ .then((response) => {
419
+ return response.json()
420
+ }).then((json) => {
421
+ $storage.set(skey, JSON.stringify(json))
422
+ // list.push.apply(list, json)
423
+ list.push(...json)
424
+ resolve(list)
425
+ }).catch((ex) => {
426
+ // (不)处理catch的异常
427
+ })
428
+ }
429
+ } else {
430
+ list.push(raw)
431
+ resolve(list)
432
+ }
433
+ })
434
+ })
435
+ },
436
+ secondToTime (second) {
437
+ const add0 = (num) => {
438
+ return isNaN(num) ? '00' : (num < 10 ? '0' + num : '' + num)
439
+ }
440
+ const hour = Math.floor(second / 3600)
441
+ const min = Math.floor((second - hour * 3600) / 60)
442
+ const sec = Math.floor(second - hour * 3600 - min * 60)
443
+ return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':')
444
+ },
445
+ nameMap: {
446
+ dragStart: isMobile ? 'touchstart' : 'mousedown',
447
+ dragMove: isMobile ? 'touchmove' : 'mousemove',
448
+ dragEnd: isMobile ? 'touchend' : 'mouseup'
449
+ }
450
+ }
451
+ source = null
452
+
453
+ t.player = {
454
+ _id: utils.random(999999),
455
+ group: true,
456
+ // 加载播放列表
457
+ load (newList) {
458
+ let d = ''
459
+
460
+ if (newList && newList.length > 0) {
461
+ if (this.options.rawList !== newList) {
462
+ this.options.rawList = newList
463
+ playlist.clear()
464
+ // 获取新列表
465
+ this.fetch()
466
+ }
467
+ } else {
468
+ // 没有列表时,隐藏按钮
469
+ d = 'none'
470
+ this.pause()
471
+ }
472
+ for (const el in buttons.el) {
473
+ setDisplay(buttons.el[el], d)
474
+ }
475
+ return this
476
+ },
477
+ fetch () {
478
+ return new Promise<boolean>((resolve, reject) => {
479
+ if (playlist.data.length > 0) {
480
+ resolve(true)
481
+ } else {
482
+ if (this.options.rawList) {
483
+ const promises = []
484
+
485
+ this.options.rawList.forEach((raw, index) => {
486
+ promises.push(new Promise((resolve, reject) => {
487
+ let group = index
488
+ let source
489
+ if (!raw.list) {
490
+ group = 0
491
+ this.group = false
492
+ source = [raw]
493
+ } else {
494
+ this.group = true
495
+ source = raw.list
496
+ }
497
+ utils.fetch(source).then((list) => {
498
+ playlist.add(group, list)
499
+ resolve(0)
500
+ })
501
+ }))
502
+ })
503
+
504
+ Promise.all(promises).then(() => {
505
+ resolve(true)
506
+ })
507
+ }
508
+ }
509
+ }).then((c) => {
510
+ if (c) {
511
+ playlist.create()
512
+ controller.create()
513
+ this.mode()
514
+ }
515
+ })
516
+ },
517
+ // 根据模式切换当前曲目index
518
+ mode () {
519
+ const total = playlist.data.length
520
+
521
+ if (!total || playlist.errnum === total) { return }
522
+
523
+ const step = controller.step === 'next' ? 1 : -1
524
+
525
+ const next = () => {
526
+ let index = playlist.index + step
527
+ if (index > total || index < 0) {
528
+ index = controller.step === 'next' ? 0 : total - 1
529
+ }
530
+ playlist.index = index
531
+ }
532
+
533
+ const random = () => {
534
+ const p = utils.random(total)
535
+ if (playlist.index !== p) {
536
+ playlist.index = p
537
+ } else {
538
+ next()
539
+ }
540
+ }
541
+
542
+ switch (this.options.mode) {
543
+ case 'random':
544
+ random()
545
+ break
546
+ case 'order':
547
+ next()
548
+ break
549
+ case 'loop':
550
+ if (controller.step) { next() }
551
+
552
+ if (playlist.index === -1) { random() }
553
+ break
554
+ }
555
+
556
+ this.init()
557
+ },
558
+ // 直接设置当前曲目index
559
+ switch (index) {
560
+ if (typeof index === 'number' &&
561
+ index !== playlist.index &&
562
+ playlist.current() &&
563
+ !playlist.current().error) {
564
+ playlist.index = index
565
+ this.init()
566
+ }
567
+ },
568
+ // 更新source为当前曲目index
569
+ init () {
570
+ const item = playlist.current()
571
+
572
+ if (!item || item.error) {
573
+ this.mode()
574
+ return
575
+ }
576
+
577
+ let playing = false
578
+ if (!source.paused) {
579
+ playing = true
580
+ this.stop()
581
+ }
582
+
583
+ source.setAttribute('src', item.url)
584
+ source.setAttribute('title', item.name + ' - ' + item.artist)
585
+ this.volume($storage.get('_PlayerVolume') || '0.7')
586
+ this.muted($storage.get('_PlayerMuted'))
587
+
588
+ progress.create()
589
+
590
+ if (this.options.type === 'audio') { preview.create() }
591
+
592
+ if (playing === true) {
593
+ this.play()
594
+ }
595
+ },
596
+ play () {
597
+ NOWPLAYING && NOWPLAYING.player.pause()
598
+
599
+ if (playlist.current().error) {
600
+ this.mode()
601
+ return
602
+ }
603
+ source.play().then(() => {
604
+ playlist.scroll()
605
+ }).catch((e) => {
606
+ // 不处理错误
607
+ })
608
+ },
609
+ pause () {
610
+ source.pause()
611
+ document.title = originTitle
612
+ },
613
+ stop () {
614
+ source.pause()
615
+ source.currentTime = 0
616
+ document.title = originTitle
617
+ },
618
+ seek (time) {
619
+ time = Math.max(time, 0)
620
+ time = Math.min(time, source.duration)
621
+ source.currentTime = time
622
+ progress.update(time / source.duration)
623
+ },
624
+ muted (status?) {
625
+ if (status === 'muted') {
626
+ source.muted = status
627
+ $storage.set('_PlayerMuted', status)
628
+ controller.update(0)
629
+ } else {
630
+ $storage.del('_PlayerMuted')
631
+ source.muted = false
632
+ controller.update(source.volume)
633
+ }
634
+ },
635
+ volume (percentage) {
636
+ if (!isNaN(percentage)) {
637
+ controller.update(percentage)
638
+ $storage.set('_PlayerVolume', percentage)
639
+ source.volume = percentage
640
+ }
641
+ },
642
+ mini () {
643
+ info.hide()
644
+ }
645
+ }
646
+
647
+ const lyrics = {
648
+ el: null,
649
+ data: null,
650
+ index: 0,
651
+ create (box) {
652
+ const current = playlist.index
653
+ // const that = this
654
+ const raw = playlist.current().lrc
655
+
656
+ const callback = (body) => {
657
+ if (current !== playlist.index) { return }
658
+
659
+ this.data = this.parse(body)
660
+
661
+ let lrc = ''
662
+ this.data.forEach((line, index) => {
663
+ lrc += '<p' + (index === 0 ? ' class="current"' : '') + '>' + line[1] + '</p>'
664
+ })
665
+
666
+ this.el = createChild(box, 'div', {
667
+ className: 'inner',
668
+ innerHTML: lrc
669
+ }, 'replace')
670
+
671
+ this.index = 0
672
+ }
673
+
674
+ if (raw.startsWith('http')) { this.fetch(raw, callback) } else { callback(raw) }
675
+ },
676
+ update (currentTime) {
677
+ if (!this.data) { return }
678
+
679
+ if (this.index > this.data.length - 1 || currentTime < this.data[this.index][0] || (!this.data[this.index + 1] || currentTime >= this.data[this.index + 1][0])) {
680
+ for (let i = 0; i < this.data.length; i++) {
681
+ if (currentTime >= this.data[i][0] && (!this.data[i + 1] || currentTime < this.data[i + 1][0])) {
682
+ this.index = i
683
+ const y = -(this.index - 1)
684
+ this.el.style.transform = 'translateY(' + y + 'rem)'
685
+ // this.el.style.webkitTransform = 'translateY(' + y + 'rem)';
686
+ this.el.getElementsByClassName('current')[0].removeClass('current')
687
+ this.el.getElementsByTagName('p')[i].addClass('current')
688
+ }
689
+ }
690
+ }
691
+ },
692
+ parse (lrc_s) {
693
+ if (lrc_s) {
694
+ lrc_s = lrc_s.replace(/([^\]^\n])\[/g, (match, p1) => {
695
+ return p1 + '\n['
696
+ })
697
+ const lyric = lrc_s.split('\n')
698
+ let lrc = []
699
+ const lyricLen = lyric.length
700
+ for (let i = 0; i < lyricLen; i++) {
701
+ // match lrc time
702
+ const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g)
703
+ // match lrc text
704
+ const lrcText = lyric[i]
705
+ .replace(/.*\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g, '')
706
+ .replace(/<(\d{2}):(\d{2})(\.(\d{2,3}))?>/g, '')
707
+ .trim()
708
+
709
+ if (lrcTimes) {
710
+ // handle multiple time tag
711
+ const timeLen = lrcTimes.length
712
+ for (let j = 0; j < timeLen; j++) {
713
+ const oneTime = /\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/.exec(lrcTimes[j])
714
+ const min2sec = <number><unknown> oneTime[1] * 60
715
+ const sec2sec = parseInt(oneTime[2])
716
+ const msec2sec = oneTime[4] ? parseInt(oneTime[4]) / ((oneTime[4] + '').length === 2 ? 100 : 1000) : 0
717
+ const lrcTime = min2sec + sec2sec + msec2sec
718
+ lrc.push([lrcTime, lrcText])
719
+ }
720
+ }
721
+ }
722
+ // sort by time
723
+ lrc = lrc.filter((item) => item[1])
724
+ lrc.sort((a, b) => a[0] - b[0])
725
+ return lrc
726
+ } else {
727
+ return []
728
+ }
729
+ },
730
+ fetch (url, callback) {
731
+ fetch(url)
732
+ .then((response) => {
733
+ return response.text()
734
+ }).then((body) => {
735
+ callback(body)
736
+ }).catch((ex) => {
737
+ // 不处理错误
738
+ })
739
+ }
740
+ }
741
+
742
+ const events = {
743
+ onerror () {
744
+ playlist.error()
745
+ t.player.mode()
746
+ },
747
+ ondurationchange () {
748
+ if (source.duration !== 1) {
749
+ progress.el.setAttribute('data-dtime', utils.secondToTime(source.duration))
750
+ }
751
+ },
752
+ onloadedmetadata () {
753
+ t.player.seek(0)
754
+ progress.el.setAttribute('data-dtime', utils.secondToTime(source.duration))
755
+ },
756
+ onplay () {
757
+ t.parentNode.addClass('playing')
758
+ showtip(this.getAttribute('title'))
759
+ NOWPLAYING = t
760
+ },
761
+ onpause () {
762
+ t.parentNode.removeClass('playing')
763
+ NOWPLAYING = null
764
+ },
765
+ ontimeupdate () {
766
+ if (!this.disableTimeupdate) {
767
+ progress.update(this.currentTime / this.duration)
768
+ lyrics.update(this.currentTime)
769
+ }
770
+ },
771
+ onended (argument) {
772
+ t.player.mode()
773
+ t.player.play()
774
+ }
775
+ }
776
+
777
+ const init = (config) => {
778
+ if (t.player.created) { return }
779
+
780
+ t.player.options = Object.assign(option, config)
781
+ t.player.options.mode = $storage.get('_PlayerMode') || t.player.options.mode
782
+
783
+ // 初始化button、controls以及click事件
784
+ buttons.create()
785
+
786
+ // 初始化audio or video
787
+ source = createChild(t, t.player.options.type, events)
788
+ // 初始化播放列表、预览、控件按钮等
789
+ info.create()
790
+
791
+ t.parentNode.addClass(t.player.options.type)
792
+
793
+ t.player.created = true
794
+ }
795
+
796
+ init(config)
797
+
798
+ return t
799
+ }