hexo-theme-shokax 0.4.6-dev6 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) 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 +76 -99
  6. package/README_en.MD +75 -91
  7. package/UsageRestrictions.md +25 -25
  8. package/_config.yml +381 -381
  9. package/_images.yml +9 -9
  10. package/eslint.config.mjs +55 -55
  11. package/languages/README.md +19 -19
  12. package/languages/default.yml +1 -1
  13. package/languages/en.yml +153 -153
  14. package/languages/ja.yml +153 -153
  15. package/languages/zh-CN.yml +153 -153
  16. package/languages/zh-HK.yml +153 -153
  17. package/languages/zh-TW.yml +153 -153
  18. package/layout/_alternate/atom.ejs +30 -30
  19. package/layout/_alternate/json.ejs +16 -16
  20. package/layout/_alternate/rss.ejs +34 -34
  21. package/layout/_mixin/breadcrumb.pug +33 -33
  22. package/layout/_mixin/card.pug +38 -38
  23. package/layout/_mixin/comment.pug +6 -6
  24. package/layout/_mixin/postmeta.pug +29 -29
  25. package/layout/_mixin/segment.pug +35 -35
  26. package/layout/_mixin/sidebar.pug +40 -40
  27. package/layout/_mixin/widgets.pug +28 -28
  28. package/layout/_partials/footer.pug +43 -43
  29. package/layout/_partials/head/head.pug +55 -55
  30. package/layout/_partials/head/head_com.pug +24 -24
  31. package/layout/_partials/head/pwa.pug +18 -18
  32. package/layout/_partials/header.pug +18 -18
  33. package/layout/_partials/layout.pug +140 -140
  34. package/layout/_partials/loading.pug +13 -13
  35. package/layout/_partials/pagination.pug +4 -4
  36. package/layout/_partials/post/copyright.pug +20 -20
  37. package/layout/_partials/post/footer.pug +17 -17
  38. package/layout/_partials/post/nav.pug +13 -13
  39. package/layout/_partials/post/post.pug +41 -41
  40. package/layout/_partials/post/reward.pug +18 -18
  41. package/layout/_partials/sidebar/menu.pug +37 -37
  42. package/layout/_partials/sidebar/overview.pug +42 -42
  43. package/layout/_partials/third-party/baidu-analytics.pug +11 -11
  44. package/layout/_partials/third-party/clarity.pug +8 -8
  45. package/layout/_partials/third-party/google-analytics.pug +9 -9
  46. package/layout/archive.pug +118 -118
  47. package/layout/category.pug +59 -59
  48. package/layout/index.pug +33 -33
  49. package/layout/page.pug +55 -55
  50. package/layout/post.pug +36 -36
  51. package/layout/tag.pug +43 -43
  52. package/package.json +71 -72
  53. package/scripts/filters/locals.js +52 -0
  54. package/scripts/filters/post.js +5 -0
  55. package/scripts/generaters/archive.js +133 -0
  56. package/scripts/generaters/config.js +51 -0
  57. package/scripts/generaters/images.js +23 -0
  58. package/scripts/generaters/index.js +107 -0
  59. package/scripts/generaters/pages.js +15 -0
  60. package/scripts/generaters/script.js +156 -0
  61. package/scripts/helpers/asset.js +73 -0
  62. package/scripts/helpers/engine.js +177 -0
  63. package/scripts/helpers/list_categories.js +80 -0
  64. package/scripts/helpers/summary_ai.js +107 -0
  65. package/scripts/helpers/symbols_count_time.js +69 -0
  66. package/scripts/plugin/check.js +32 -0
  67. package/scripts/plugin/index.js +81 -0
  68. package/scripts/plugin/lib/injects-point.js +20 -0
  69. package/scripts/plugin/lib/injects.js +89 -0
  70. package/scripts/tags/links.js +44 -0
  71. package/scripts/tags/media.js +19 -0
  72. package/scripts/utils.js +13 -0
  73. package/source/assets/algolia_logo.svg +9 -9
  74. package/source/assets/logo.svg +16 -16
  75. package/source/css/_colors.styl +207 -207
  76. package/source/css/_common/components/components.styl +6 -6
  77. package/source/css/_common/components/highlight/highlight.styl +357 -357
  78. package/source/css/_common/components/highlight/operation.styl +21 -21
  79. package/source/css/_common/components/pages/collapse.styl +119 -119
  80. package/source/css/_common/components/pages/home.styl +391 -391
  81. package/source/css/_common/components/pages/pages.styl +56 -56
  82. package/source/css/_common/components/pages/tag-cloud.styl +12 -12
  83. package/source/css/_common/components/post/breadcrumb.styl +39 -39
  84. package/source/css/_common/components/post/copyright.styl +41 -41
  85. package/source/css/_common/components/post/expand.styl +263 -263
  86. package/source/css/_common/components/post/footer.styl +11 -11
  87. package/source/css/_common/components/post/header.styl +79 -79
  88. package/source/css/_common/components/post/nav.styl +64 -64
  89. package/source/css/_common/components/post/post.styl +29 -29
  90. package/source/css/_common/components/post/reward.styl +50 -50
  91. package/source/css/_common/components/post/rtl.styl +12 -12
  92. package/source/css/_common/components/post/tags.styl +39 -39
  93. package/source/css/_common/components/tags/collapse.styl +72 -72
  94. package/source/css/_common/components/tags/container.styl +49 -49
  95. package/source/css/_common/components/tags/label.styl +12 -12
  96. package/source/css/_common/components/tags/links.styl +77 -77
  97. package/source/css/_common/components/tags/list.styl +131 -131
  98. package/source/css/_common/components/tags/note.styl +70 -70
  99. package/source/css/_common/components/tags/player.styl +361 -361
  100. package/source/css/_common/components/tags/quiz.styl +200 -200
  101. package/source/css/_common/components/tags/tabs.styl +89 -89
  102. package/source/css/_common/components/tags/tags.styl +9 -9
  103. package/source/css/_common/components/third-party/loading.styl +222 -222
  104. package/source/css/_common/components/third-party/mermaid/class.styl +90 -90
  105. package/source/css/_common/components/third-party/mermaid/flowchart.styl +72 -72
  106. package/source/css/_common/components/third-party/mermaid/gantt.styl +251 -251
  107. package/source/css/_common/components/third-party/mermaid/git.styl +7 -7
  108. package/source/css/_common/components/third-party/mermaid/mermaid.styl +37 -37
  109. package/source/css/_common/components/third-party/mermaid/pie.styl +9 -9
  110. package/source/css/_common/components/third-party/mermaid/sequence.styl +95 -95
  111. package/source/css/_common/components/third-party/mermaid/state.styl +130 -130
  112. package/source/css/_common/components/third-party/pace.styl +18 -18
  113. package/source/css/_common/components/third-party/search.styl +167 -167
  114. package/source/css/_common/components/third-party/theme.styl +151 -151
  115. package/source/css/_common/components/third-party/third-party.styl +22 -22
  116. package/source/css/_common/components/third-party/widgets.styl +57 -57
  117. package/source/css/_common/outline/footer/footer.styl +67 -67
  118. package/source/css/_common/outline/header/brand.styl +77 -77
  119. package/source/css/_common/outline/header/header.styl +20 -20
  120. package/source/css/_common/outline/header/image.styl +85 -85
  121. package/source/css/_common/outline/header/menu.styl +117 -117
  122. package/source/css/_common/outline/header/nav.styl +81 -81
  123. package/source/css/_common/outline/header/right.styl +15 -15
  124. package/source/css/_common/outline/header/tool.styl +207 -207
  125. package/source/css/_common/outline/header/waves.styl +57 -57
  126. package/source/css/_common/outline/mobile.styl +46 -46
  127. package/source/css/_common/outline/outline.styl +78 -78
  128. package/source/css/_common/outline/sidebar/author.styl +59 -59
  129. package/source/css/_common/outline/sidebar/dimmer.styl +23 -23
  130. package/source/css/_common/outline/sidebar/menu.styl +63 -63
  131. package/source/css/_common/outline/sidebar/quick.styl +43 -43
  132. package/source/css/_common/outline/sidebar/related.styl +56 -56
  133. package/source/css/_common/outline/sidebar/sidebar.styl +80 -80
  134. package/source/css/_common/outline/sidebar/social.styl +69 -69
  135. package/source/css/_common/outline/sidebar/state.styl +37 -37
  136. package/source/css/_common/outline/sidebar/tab.styl +71 -71
  137. package/source/css/_common/outline/sidebar/toc.styl +47 -47
  138. package/source/css/_common/scaffolding/animate.styl +322 -322
  139. package/source/css/_common/scaffolding/base.styl +190 -190
  140. package/source/css/_common/scaffolding/buttons.styl +48 -48
  141. package/source/css/_common/scaffolding/divider.styl +36 -36
  142. package/source/css/_common/scaffolding/iconfont.styl +443 -443
  143. package/source/css/_common/scaffolding/normalize.styl +273 -273
  144. package/source/css/_common/scaffolding/pagination.styl +81 -81
  145. package/source/css/_common/scaffolding/ribbon.styl +38 -38
  146. package/source/css/_common/scaffolding/scaffolding.styl +14 -14
  147. package/source/css/_common/scaffolding/scrollbar.styl +37 -37
  148. package/source/css/_common/scaffolding/tables.styl +50 -50
  149. package/source/css/_common/scaffolding/tip.styl +19 -19
  150. package/source/css/_common/scaffolding/toggles.styl +59 -59
  151. package/source/css/_iconfont.styl +455 -455
  152. package/source/css/_mixins.styl +148 -148
  153. package/source/css/_variables.styl +89 -89
  154. package/source/css/app.styl +41 -41
  155. package/source/css/mermaid.styl +5 -5
  156. package/source/css/optimize.styl +5 -5
  157. package/source/js/_app/components/comments.ts +88 -88
  158. package/source/js/_app/components/sidebar.ts +239 -239
  159. package/source/js/_app/components/tcomments.ts +54 -54
  160. package/source/js/_app/globals/globalVars.ts +99 -99
  161. package/source/js/_app/globals/handles.ts +124 -124
  162. package/source/js/_app/globals/themeColor.ts +63 -63
  163. package/source/js/_app/globals/thirdparty.ts +63 -63
  164. package/source/js/_app/globals/tools.ts +75 -75
  165. package/source/js/_app/library/anime.ts +110 -110
  166. package/source/js/_app/library/declare.d.ts +128 -128
  167. package/source/js/_app/library/dom.ts +28 -28
  168. package/source/js/_app/library/loadFile.ts +47 -47
  169. package/source/js/_app/library/proto.ts +137 -137
  170. package/source/js/_app/library/scriptPjax.ts +72 -72
  171. package/source/js/_app/library/storage.ts +12 -12
  172. package/source/js/_app/library/vue.ts +49 -49
  173. package/source/js/_app/page/common.ts +43 -43
  174. package/source/js/_app/page/fancybox.ts +72 -72
  175. package/source/js/_app/page/post.ts +266 -266
  176. package/source/js/_app/page/search.ts +115 -115
  177. package/source/js/_app/page/tab.ts +60 -60
  178. package/source/js/_app/pjax/domInit.ts +97 -97
  179. package/source/js/_app/pjax/refresh.ts +140 -140
  180. package/source/js/_app/pjax/siteInit.ts +106 -106
  181. package/source/js/_app/player.ts +798 -798
  182. package/toolbox/compiler.mjs +10 -19
  183. package/toolbox/hoistdep.mjs +5 -5
  184. package/toolbox/lib.mjs +63 -63
  185. package/index.html +0 -231
  186. package/meta.json +0 -6431
@@ -1,798 +1,798 @@
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
- }
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
+ }