hexo-theme-shokax 0.4.4 → 0.4.6-beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. package/CODE_OF_CONDUCT.md +128 -128
  2. package/CONTRIBUTING.md +58 -58
  3. package/LICENSE +660 -660
  4. package/LICENSE-shoka +21 -21
  5. package/README.md +99 -98
  6. package/README_en.MD +91 -90
  7. package/UsageRestrictions.md +25 -25
  8. package/_config.yml +381 -388
  9. package/_images.yml +9 -9
  10. package/eslint.config.mjs +55 -55
  11. package/index.html +231 -0
  12. package/languages/README.md +19 -19
  13. package/languages/default.yml +1 -1
  14. package/languages/en.yml +153 -153
  15. package/languages/ja.yml +153 -153
  16. package/languages/zh-CN.yml +153 -153
  17. package/languages/zh-HK.yml +153 -153
  18. package/languages/zh-TW.yml +153 -153
  19. package/layout/_alternate/atom.ejs +30 -30
  20. package/layout/_alternate/json.ejs +16 -16
  21. package/layout/_alternate/rss.ejs +34 -34
  22. package/layout/_mixin/breadcrumb.pug +33 -33
  23. package/layout/_mixin/card.pug +38 -38
  24. package/layout/_mixin/comment.pug +6 -6
  25. package/layout/_mixin/postmeta.pug +29 -29
  26. package/layout/_mixin/segment.pug +35 -35
  27. package/layout/_mixin/sidebar.pug +40 -40
  28. package/layout/_mixin/widgets.pug +28 -28
  29. package/layout/_partials/footer.pug +43 -43
  30. package/layout/_partials/head/head.pug +55 -56
  31. package/layout/_partials/head/head_com.pug +24 -24
  32. package/layout/_partials/head/pwa.pug +18 -18
  33. package/layout/_partials/header.pug +18 -18
  34. package/layout/_partials/layout.pug +140 -140
  35. package/layout/_partials/loading.pug +13 -13
  36. package/layout/_partials/pagination.pug +4 -4
  37. package/layout/_partials/post/copyright.pug +20 -20
  38. package/layout/_partials/post/footer.pug +17 -17
  39. package/layout/_partials/post/nav.pug +13 -13
  40. package/layout/_partials/post/post.pug +41 -41
  41. package/layout/_partials/post/reward.pug +18 -18
  42. package/layout/_partials/sidebar/menu.pug +37 -37
  43. package/layout/_partials/sidebar/overview.pug +42 -42
  44. package/layout/_partials/third-party/baidu-analytics.pug +11 -11
  45. package/layout/_partials/third-party/clarity.pug +8 -8
  46. package/layout/_partials/third-party/google-analytics.pug +9 -9
  47. package/layout/archive.pug +118 -118
  48. package/layout/category.pug +59 -59
  49. package/layout/index.pug +33 -33
  50. package/layout/page.pug +55 -55
  51. package/layout/post.pug +36 -36
  52. package/layout/tag.pug +43 -43
  53. package/meta.json +6431 -0
  54. package/package.json +72 -70
  55. package/scripts/helpers/engine.js +10 -0
  56. package/scripts/plugin/check.js +2 -2
  57. package/scripts/tags/links.js +4 -4
  58. package/source/assets/algolia_logo.svg +9 -9
  59. package/source/assets/logo.svg +16 -16
  60. package/source/css/_colors.styl +207 -207
  61. package/source/css/_common/components/components.styl +6 -6
  62. package/source/css/_common/components/highlight/highlight.styl +357 -357
  63. package/source/css/_common/components/highlight/operation.styl +21 -21
  64. package/source/css/_common/components/pages/collapse.styl +119 -119
  65. package/source/css/_common/components/pages/home.styl +391 -391
  66. package/source/css/_common/components/pages/pages.styl +56 -56
  67. package/source/css/_common/components/pages/tag-cloud.styl +12 -12
  68. package/source/css/_common/components/post/breadcrumb.styl +39 -39
  69. package/source/css/_common/components/post/copyright.styl +41 -41
  70. package/source/css/_common/components/post/expand.styl +263 -263
  71. package/source/css/_common/components/post/footer.styl +11 -11
  72. package/source/css/_common/components/post/header.styl +79 -79
  73. package/source/css/_common/components/post/nav.styl +64 -64
  74. package/source/css/_common/components/post/post.styl +29 -29
  75. package/source/css/_common/components/post/reward.styl +50 -50
  76. package/source/css/_common/components/post/rtl.styl +12 -12
  77. package/source/css/_common/components/post/tags.styl +39 -39
  78. package/source/css/_common/components/tags/collapse.styl +72 -72
  79. package/source/css/_common/components/tags/container.styl +49 -49
  80. package/source/css/_common/components/tags/label.styl +12 -12
  81. package/source/css/_common/components/tags/links.styl +77 -77
  82. package/source/css/_common/components/tags/list.styl +131 -131
  83. package/source/css/_common/components/tags/note.styl +70 -70
  84. package/source/css/_common/components/tags/player.styl +361 -361
  85. package/source/css/_common/components/tags/quiz.styl +200 -200
  86. package/source/css/_common/components/tags/tabs.styl +89 -89
  87. package/source/css/_common/components/tags/tags.styl +9 -9
  88. package/source/css/_common/components/third-party/loading.styl +222 -222
  89. package/source/css/_common/components/third-party/mermaid/class.styl +90 -90
  90. package/source/css/_common/components/third-party/mermaid/flowchart.styl +72 -72
  91. package/source/css/_common/components/third-party/mermaid/gantt.styl +251 -251
  92. package/source/css/_common/components/third-party/mermaid/git.styl +7 -7
  93. package/source/css/_common/components/third-party/mermaid/mermaid.styl +37 -37
  94. package/source/css/_common/components/third-party/mermaid/pie.styl +9 -9
  95. package/source/css/_common/components/third-party/mermaid/sequence.styl +95 -95
  96. package/source/css/_common/components/third-party/mermaid/state.styl +130 -130
  97. package/source/css/_common/components/third-party/pace.styl +18 -18
  98. package/source/css/_common/components/third-party/search.styl +167 -167
  99. package/source/css/_common/components/third-party/theme.styl +151 -151
  100. package/source/css/_common/components/third-party/third-party.styl +22 -22
  101. package/source/css/_common/components/third-party/widgets.styl +57 -57
  102. package/source/css/_common/outline/footer/footer.styl +67 -67
  103. package/source/css/_common/outline/header/brand.styl +77 -77
  104. package/source/css/_common/outline/header/header.styl +20 -20
  105. package/source/css/_common/outline/header/image.styl +85 -85
  106. package/source/css/_common/outline/header/menu.styl +117 -117
  107. package/source/css/_common/outline/header/nav.styl +81 -81
  108. package/source/css/_common/outline/header/right.styl +15 -15
  109. package/source/css/_common/outline/header/tool.styl +207 -207
  110. package/source/css/_common/outline/header/waves.styl +57 -57
  111. package/source/css/_common/outline/mobile.styl +46 -46
  112. package/source/css/_common/outline/outline.styl +78 -78
  113. package/source/css/_common/outline/sidebar/author.styl +59 -59
  114. package/source/css/_common/outline/sidebar/dimmer.styl +23 -23
  115. package/source/css/_common/outline/sidebar/menu.styl +63 -63
  116. package/source/css/_common/outline/sidebar/quick.styl +43 -43
  117. package/source/css/_common/outline/sidebar/related.styl +56 -56
  118. package/source/css/_common/outline/sidebar/sidebar.styl +80 -80
  119. package/source/css/_common/outline/sidebar/social.styl +69 -69
  120. package/source/css/_common/outline/sidebar/state.styl +37 -37
  121. package/source/css/_common/outline/sidebar/tab.styl +71 -71
  122. package/source/css/_common/outline/sidebar/toc.styl +47 -47
  123. package/source/css/_common/scaffolding/animate.styl +322 -322
  124. package/source/css/_common/scaffolding/base.styl +190 -190
  125. package/source/css/_common/scaffolding/buttons.styl +48 -48
  126. package/source/css/_common/scaffolding/divider.styl +36 -36
  127. package/source/css/_common/scaffolding/iconfont.styl +443 -443
  128. package/source/css/_common/scaffolding/normalize.styl +273 -273
  129. package/source/css/_common/scaffolding/pagination.styl +81 -81
  130. package/source/css/_common/scaffolding/ribbon.styl +38 -38
  131. package/source/css/_common/scaffolding/scaffolding.styl +14 -14
  132. package/source/css/_common/scaffolding/scrollbar.styl +37 -37
  133. package/source/css/_common/scaffolding/tables.styl +50 -50
  134. package/source/css/_common/scaffolding/tip.styl +19 -19
  135. package/source/css/_common/scaffolding/toggles.styl +59 -59
  136. package/source/css/_iconfont.styl +455 -455
  137. package/source/css/_mixins.styl +148 -148
  138. package/source/css/_variables.styl +89 -89
  139. package/source/css/app.styl +41 -41
  140. package/source/css/mermaid.styl +5 -5
  141. package/source/css/optimize.styl +5 -5
  142. package/source/js/_app/components/comments.js +59 -0
  143. package/source/js/_app/components/comments.ts +88 -89
  144. package/source/js/_app/components/sidebar.js +244 -0
  145. package/source/js/_app/components/sidebar.ts +239 -239
  146. package/source/js/_app/components/tcomments.js +47 -0
  147. package/source/js/_app/components/tcomments.ts +54 -55
  148. package/source/js/_app/globals/globalVars.ts +99 -99
  149. package/source/js/_app/globals/handles.js +105 -0
  150. package/source/js/_app/globals/handles.ts +124 -123
  151. package/source/js/_app/globals/themeColor.ts +63 -64
  152. package/source/js/_app/globals/thirdparty.ts +63 -63
  153. package/source/js/_app/globals/tools.ts +75 -75
  154. package/source/js/_app/library/anime.ts +110 -110
  155. package/source/js/_app/library/declare.d.ts +128 -133
  156. package/source/js/_app/library/dom.ts +28 -26
  157. package/source/js/_app/library/loadFile.js +43 -0
  158. package/source/js/_app/library/loadFile.ts +47 -43
  159. package/source/js/_app/library/proto.ts +137 -141
  160. package/source/js/_app/library/scriptPjax.ts +72 -72
  161. package/source/js/_app/library/storage.ts +12 -12
  162. package/source/js/_app/library/vue.js +52 -0
  163. package/source/js/_app/library/vue.ts +49 -50
  164. package/source/js/_app/page/common.js +45 -0
  165. package/source/js/_app/page/common.ts +43 -42
  166. package/source/js/_app/page/fancybox.js +70 -0
  167. package/source/js/_app/page/fancybox.ts +72 -71
  168. package/source/js/_app/page/post.js +253 -0
  169. package/source/js/_app/page/post.ts +266 -266
  170. package/source/js/_app/page/search.js +111 -0
  171. package/source/js/_app/page/search.ts +115 -115
  172. package/source/js/_app/page/tab.ts +60 -60
  173. package/source/js/_app/pjax/domInit.js +80 -0
  174. package/source/js/_app/pjax/domInit.ts +97 -97
  175. package/source/js/_app/pjax/refresh.js +137 -0
  176. package/source/js/_app/pjax/refresh.ts +140 -138
  177. package/source/js/_app/pjax/siteInit.js +115 -0
  178. package/source/js/_app/pjax/siteInit.ts +106 -104
  179. package/source/js/_app/player.js +777 -0
  180. package/source/js/_app/player.ts +798 -799
  181. package/toolbox/compiler.mjs +73 -0
  182. package/toolbox/hoistdep.mjs +6 -0
  183. package/toolbox/lib.mjs +40 -0
@@ -1,799 +1,798 @@
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
- }
1
+ import { CONFIG, originTitle } from './globals/globalVars'
2
+ import { showtip } from './globals/tools'
3
+ import { pageScroll } from './library/anime'
4
+ import { $storage } from './library/storage'
5
+ import { tabFormat } from './page/tab'
6
+ import { createChild, getLeft, getWidth, setDisplay, setWidth } from './library/proto'
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] = createChild(t, '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] = createChild(that.el, 'div', opt)
68
+ })
69
+
70
+ that.btns.volume.bar = that.btns.volume.querySelector('.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
+ setWidth(controller.btns.volume.bar, Math.floor(percent * 100) + '%')
134
+ },
135
+ percent (e, el) {
136
+ let percentage = ((e.clientX || e.changedTouches[0].clientX) - getLeft(el)) / getWidth(el)
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 = createChild(current, 'div', {
155
+ className: 'progress'
156
+ });
157
+
158
+ (progress.el as HTMLElement).setAttribute('data-dtime', utils.secondToTime(0))
159
+
160
+ progress.bar = createChild(progress.el, '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
+ setWidth(progress.bar, 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) - getLeft(el)) / getWidth(el)
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 as HTMLElement).querySelector('.cover').addEventListener('click', t.player.options.events['play-pause'])
222
+
223
+ lyrics.create((preview.el as HTMLElement).querySelector('.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 = document.getElementById(id)
260
+ if (!tab) {
261
+ tab = createChild(el, '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 = createChild(tab.querySelector('ol'), '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.querySelector('li.active')
302
+ li && li.removeClass('active')
303
+ let tab = this.el.querySelector('.tab.active')
304
+ tab && tab.removeClass('active')
305
+ li = this.el.querySelectorAll('.nav li')[item.group]
306
+ li && li.addClass('active')
307
+ tab = this.el.querySelectorAll('.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 = createChild(t, '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.querySelector('.preview')
338
+ playlist.el = this.el.querySelector('.playlist')
339
+ controller.el = this.el.querySelector('.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
+ setDisplay(buttons.el[el], 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 = createChild(box, '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 = createChild(t, 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
+ }