hexo-theme-shokax 0.3.12 → 0.4.0-alpha.1

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