diff --git a/demo/demo.js b/demo/demo.js index 0555263..e307293 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -186,4 +186,20 @@ const ap7 = new APlayer({ } } } +}); + +let ap8; +$.ajax({ + url: 'https://api.i-meto.com/meting/api?server=netease&type=playlist&id=35798529', + success: function (list) { + ap8 = new APlayer({ + element: document.getElementById('player8'), + mutex: true, + theme: '#ad7a86', + order: 'random', + lrcType: 3, + fixed: true, + audio: JSON.parse(list) + }); + } }); \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index c0b26e4..1934364 100644 --- a/demo/index.html +++ b/demo/index.html @@ -12,7 +12,7 @@ max-width: 32rem; margin-left: auto; margin-right: auto; - margin-bottom: 50px; + margin-bottom: 150px; } h1 { font-size: 54px; @@ -96,10 +96,14 @@

HLS

+
+ \ No newline at end of file diff --git a/package.json b/package.json index 1899203..27a0631 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ }, "dependencies": { "balloon-css": "^0.5.0", - "promise-polyfill": "7.1.0" + "promise-polyfill": "7.1.0", + "smoothscroll": "0.4.0" } } diff --git a/src/assets/right.svg b/src/assets/right.svg new file mode 100644 index 0000000..0d30348 --- /dev/null +++ b/src/assets/right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/skip.svg b/src/assets/skip.svg new file mode 100644 index 0000000..e7a23dd --- /dev/null +++ b/src/assets/skip.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/css/index.scss b/src/css/index.scss index 5923ab4..4b13a19 100644 --- a/src/css/index.scss +++ b/src/css/index.scss @@ -73,6 +73,114 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; } } + &.aplayer-fixed { + position: fixed; + bottom: 0; + left: 0; + right: 0; + margin: 0; + z-index: 99; + overflow: visible; + max-width: 400px; + box-shadow: none; + + .aplayer-list { + margin-bottom: 66px; + } + + .aplayer-body { + position: fixed; + bottom: 0; + left: 0; + right: 0; + margin: 0; + z-index: 99; + background: #fff; + padding-right: 18px; + transition: all 0.3s ease; + max-width: 400px; + } + + .aplayer-lrc { + display: block; + position: fixed; + bottom: 10px; + left: 0; + right: 0; + margin: 0; + z-index: 98; + pointer-events: none; + text-shadow: 0 0 1px #fff, 0 0 10px #fff; + + &:before, + &:after { + display: none; + } + } + + .aplayer-info { + transform: scaleX(1); + transform-origin: 0 0; + transition: all 0.3s ease; + border-bottom: none; + border-top: 1px solid #e9e9e9; + + .aplayer-music { + width: calc(100% - 105px); + } + } + + .aplayer-miniswitcher { + display: block; + } + + &.aplayer-narrow { + .aplayer-info { + display: block; + transform: scaleX(0); + } + .aplayer-body { + width: $aplayer-height !important; + } + + .aplayer-miniswitcher .aplayer-icon { + transform: rotateY(0); + } + } + + .aplayer-icon-back, + .aplayer-icon-play, + .aplayer-icon-forward { + display: inline-block; + } + + .aplayer-icon-back, + .aplayer-icon-play, + .aplayer-icon-forward, + .aplayer-icon-menu { + position: absolute; + bottom: 27px; + width: 20px; + height: 20px; + } + + .aplayer-icon-back { + right: 75px; + } + + .aplayer-icon-play { + right: 50px; + } + + .aplayer-icon-forward { + right: 25px; + } + + .aplayer-icon-menu { + right: 0; + } + } + &.aplayer-mobile { .aplayer-icon-volume-down { display: none; @@ -112,17 +220,24 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; padding: 0; font-size: 12px; margin: 0; - display: inline; + display: inline-block; path { transition: all .2s ease-in-out; } } - .aplayer-icon-order { + .aplayer-icon-order, + .aplayer-icon-back, + .aplayer-icon-play, + .aplayer-icon-forward { display: none; } + .aplayer-icon-forward { + transform: rotate(180deg); + } + .aplayer-lrc-content { display: none; } @@ -428,6 +543,7 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; opacity: 1; overflow: visible; height: initial !important; + min-height: 16px; } } @@ -447,7 +563,7 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; overflow: hidden; &.aplayer-list-hide { - height: 0 !important; + max-height: 0 !important; } ol { @@ -460,10 +576,6 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; width: 5px; } - &::-webkit-scrollbar-track { - background-color: #f9f9f9; - } - &::-webkit-scrollbar-thumb { border-radius: 3px; background-color: #eee; @@ -540,6 +652,35 @@ $aplayer-height-lrc: $aplayer-height + $lrc-height - 6; background-color: #f4f4f5; color: #909399; } + + .aplayer-miniswitcher { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + height: 100%; + background: #e6e6e6; + width: 18px; + border-radius: 0 2px 2px 0; + + .aplayer-icon { + height: 100%; + width: 100%; + transform: rotateY(180deg); + transition: all 0.3s ease; + + path { + fill: #666; + } + + &:hover { + path { + fill: #000; + } + } + } + } } @keyframes aplayer-roll { diff --git a/src/js/controller.js b/src/js/controller.js index 5c74942..fe68c67 100644 --- a/src/js/controller.js +++ b/src/js/controller.js @@ -13,6 +13,8 @@ class Controller { if (!utils.isMobile) { this.initVolumeButton(); } + this.initMiniSwitcher(); + this.initSkipButton(); } initPlayButton () { @@ -64,7 +66,7 @@ class Controller { }); const thumbMove = (e) => { - let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - utils.getElementViewTop(this.player.template.volumeBar)) / this.player.template.volumeBar.clientHeight; + let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - utils.getElementViewTop(this.player.template.volumeBar, this.player.options.fixed)) / this.player.template.volumeBar.clientHeight; percentage = Math.max(percentage, 0); percentage = Math.min(percentage, 1); this.player.volume(percentage); @@ -74,7 +76,7 @@ class Controller { this.player.template.volumeBarWrap.classList.remove('aplayer-volume-bar-wrap-active'); document.removeEventListener(utils.nameMap.dragEnd, thumbUp); document.removeEventListener(utils.nameMap.dragMove, thumbMove); - let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - utils.getElementViewTop(this.player.template.volumeBar)) / this.player.template.volumeBar.clientHeight; + let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - utils.getElementViewTop(this.player.template.volumeBar, this.player.options.fixed)) / this.player.template.volumeBar.clientHeight; percentage = Math.max(percentage, 0); percentage = Math.min(percentage, 1); this.player.volume(percentage); @@ -130,12 +132,28 @@ class Controller { } initMenuButton () { - this.player.template.list.style.height = 33 * this.player.options.audio.length - 1 + 'px'; - this.player.template.listOl.style.height = 33 * this.player.options.audio.length - 1 + 'px'; this.player.template.menu.addEventListener('click', () => { this.player.list.toggle(); }); } + + initMiniSwitcher () { + this.player.template.miniSwitcher.addEventListener('click', () => { + this.player.setMode(this.player.mode === 'mini' ? 'normal' : 'mini'); + }); + } + + initSkipButton () { + this.player.template.skipBackButton.addEventListener('click', () => { + this.player.skipBack(); + }); + this.player.template.skipForwardButton.addEventListener('click', () => { + this.player.skipForward(); + }); + this.player.template.skipPlayButton.addEventListener('click', () => { + this.player.toggle(); + }); + } } export default Controller; \ No newline at end of file diff --git a/src/js/icons.js b/src/js/icons.js index 2f6cdde..f06091a 100644 --- a/src/js/icons.js +++ b/src/js/icons.js @@ -10,6 +10,8 @@ import loopAll from '../assets/loop-all.svg'; import loopOne from '../assets/loop-one.svg'; import loopNone from '../assets/loop-none.svg'; import loading from '../assets/loading.svg'; +import right from '../assets/right.svg'; +import skip from '../assets/skip.svg'; const Icons = { play: play, @@ -24,6 +26,8 @@ const Icons = { loopOne: loopOne, loopNone: loopNone, loading: loading, + right: right, + skip: skip, }; export default Icons; diff --git a/src/js/list.js b/src/js/list.js index 133113a..f97473c 100644 --- a/src/js/list.js +++ b/src/js/list.js @@ -1,5 +1,6 @@ import tplListItem from '../template/list-item.art'; import utils from './utils'; +import smoothScroll from 'smoothscroll'; class List { constructor (player) { @@ -33,6 +34,7 @@ class List { show () { this.player.events.trigger('listshow'); this.player.template.list.classList.remove('aplayer-list-hide'); + this.player.template.listOl.scrollTop = this.index * 33; } hide () { @@ -65,8 +67,6 @@ class List { if (wasSingle && this.audios.length > 1) { this.player.container.classList.add('aplayer-withlist'); } - this.player.template.list.style.height = this.audios.length * 33 - 1 + 'px'; - this.player.template.listOl.style.height = this.audios.length * 33 - 1 + 'px'; this.player.randomOrder = utils.randomOrder(this.audios.length); this.player.template.listCurs = this.player.container.querySelectorAll('.aplayer-list-cur'); @@ -110,8 +110,6 @@ class List { if (this.audios.length === 1) { this.player.container.classList.remove('aplayer-withlist'); } - this.player.template.list.style.height = this.audios.length * 33 - 1 + 'px'; - this.player.template.listOl.style.height = this.audios.length * 33 - 1 + 'px'; this.player.template.listCurs = this.player.container.querySelectorAll('.aplayer-list-cur'); } @@ -141,11 +139,12 @@ class List { } this.player.container.querySelectorAll('.aplayer-list li')[this.index].classList.add('aplayer-list-light'); - this.player.template.list.scrollTop = this.index * 33; + smoothScroll(this.index * 33, 500, null, this.player.template.listOl); this.player.setAudio(audio); this.player.lrc && this.player.lrc.switch(this.index); + this.player.lrc && this.player.lrc.update(0); // set duration time if (this.player.duration !== 1) { // compatibility: Android browsers will output 1 at first diff --git a/src/js/lrc.js b/src/js/lrc.js index bf9e7e9..b3e69b0 100644 --- a/src/js/lrc.js +++ b/src/js/lrc.js @@ -83,7 +83,7 @@ class Lrc { if (lrc_s) { lrc_s = lrc_s.replace(/([^\]^\n])\[/g, (match, p1) => p1 + '\n['); const lyric = lrc_s.split('\n'); - const lrc = []; + let lrc = []; const lyricLen = lyric.length; for (let i = 0; i < lyricLen; i++) { // match lrc time @@ -105,6 +105,7 @@ class Lrc { } } // sort by time + lrc = lrc.filter((item) => item[1]); lrc.sort((a, b) => a[0] - b[0]); return lrc; } diff --git a/src/js/options.js b/src/js/options.js index 2bda39f..0eee046 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -3,7 +3,8 @@ export default (options) => { // default options const defaultOption = { container: options.element || document.getElementsByClassName('aplayer')[0], - mini: options.narrow || false, + mini: options.narrow || options.fixed || false, + fixed: false, autoplay: false, mutex: true, lrcType: options.showlrc || options.lrc || 0, @@ -12,8 +13,8 @@ export default (options) => { loop: 'all', order: 'list', volume: 0.7, - listFolded: false, - listMaxHeight: options.listmaxheight, + listFolded: options.fixed, + listMaxHeight: options.listmaxheight || '250px', audio: options.music || [], storageName: 'aplayer-setting' }; diff --git a/src/js/player.js b/src/js/player.js index 85cdb3b..b4e85b3 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -32,7 +32,7 @@ class APlayer { this.randomOrder = utils.randomOrder(this.options.audio.length); this.container.classList.add('aplayer'); - if (this.options.lrcType) { + if (this.options.lrcType && !this.options.fixed) { this.container.classList.add('aplayer-withlrc'); } if (this.options.audio.length > 1) { @@ -45,9 +45,6 @@ class APlayer { if (this.arrow) { this.container.classList.add('aplayer-arrow'); } - if (this.options.mini) { - this.setMode('mini'); - } // save lrc this.container = this.options.container; @@ -66,6 +63,14 @@ class APlayer { randomOrder: this.randomOrder, }); + if (this.options.fixed) { + this.container.classList.add('aplayer-fixed'); + this.template.body.style.width = this.template.body.offsetWidth - 18 + 'px'; + } + if (this.options.mini) { + this.setMode('mini'); + this.template.info.style.display = 'block'; + } if (this.template.info.offsetWidth < 200) { this.template.time.classList.add('aplayer-time-narrow'); } @@ -150,6 +155,15 @@ class APlayer { // audio download error: an error occurs this.on('error', () => { if (this.list.audios.length) { + this.notice('An audio error has occurred, player will skip forward in 2 seconds.'); + setTimeout(() => { + this.skipForward(); + if (!this.paused) { + this.play(); + } + }, 2000); + } + else { this.notice('An audio error has occurred.'); } }); @@ -169,11 +183,11 @@ class APlayer { } else if (this.options.order === 'random') { if (this.randomOrder.indexOf(this.list.index) < this.randomOrder.length - 1) { - this.list.switch(this.nextRandomNum()); + this.list.switch(this.nextIndex()); this.play(); } else { - this.list.switch(this.nextRandomNum()); + this.list.switch(this.nextIndex()); this.pause(); } } @@ -183,12 +197,7 @@ class APlayer { this.play(); } else if (this.options.loop === 'all') { - if (this.options.order === 'list') { - this.list.switch((this.list.index + 1) % this.list.audios.length); - } - else if (this.options.order === 'random') { - this.list.switch(this.nextRandomNum()); - } + this.skipForward(); this.play(); } }); @@ -277,6 +286,7 @@ class APlayer { setTimeout(() => { this.template.button.innerHTML = Icons.pause; }, 100); + this.template.skipPlayButton.innerHTML = Icons.pause; } this.timer.enable('loading'); @@ -297,8 +307,7 @@ class APlayer { if (playPromise) { playPromise.catch((e) => { console.warn(e); - if (e.name === 'NotAllowedError' || - e.name === 'NotSupportedError') { + if (e.name === 'NotAllowedError') { this.setUIPaused(); } }); @@ -315,6 +324,7 @@ class APlayer { setTimeout(() => { this.template.button.innerHTML = Icons.play; }, 100); + this.template.skipPlayButton.innerHTML = Icons.play; } this.container.classList.remove('aplayer-loading'); @@ -381,24 +391,6 @@ class APlayer { } } - /** - * get next random number - */ - nextRandomNum () { - if (this.list.audios.length > 1) { - const index = this.randomOrder.indexOf(this.list.index); - if (index === this.randomOrder.length - 1) { - return this.randomOrder[0]; - } - else { - return this.randomOrder[index + 1]; - } - } - else { - return 0; - } - } - // abandoned switchAudio (index) { this.list.switch(index); @@ -442,15 +434,63 @@ class APlayer { if (this.noticeTime) { clearTimeout(this.noticeTime); } - this.events.trigger('notice_show', text); + this.events.trigger('noticeshow', text); if (time) { this.noticeTime = setTimeout(() => { this.template.notice.style.opacity = 0; - this.events.trigger('notice_hide'); + this.events.trigger('noticehide'); }, time); } } + prevIndex () { + if (this.list.audios.length > 1) { + if (this.options.order === 'list') { + return this.list.index - 1 < 0 ? this.list.audios.length - 1 : this.list.index - 1; + } + else if (this.options.order === 'random') { + const index = this.randomOrder.indexOf(this.list.index); + if (index === 0) { + return this.randomOrder[this.randomOrder.length - 1]; + } + else { + return this.randomOrder[index - 1]; + } + } + } + else { + return 0; + } + } + + nextIndex () { + if (this.list.audios.length > 1) { + if (this.options.order === 'list') { + return (this.list.index + 1) % this.list.audios.length; + } + else if (this.options.order === 'random') { + const index = this.randomOrder.indexOf(this.list.index); + if (index === this.randomOrder.length - 1) { + return this.randomOrder[0]; + } + else { + return this.randomOrder[index + 1]; + } + } + } + else { + return 0; + } + } + + skipBack () { + this.list.switch(this.prevIndex()); + } + + skipForward () { + this.list.switch(this.nextIndex()); + } + static get version () { /* global APLAYER_VERSION */ return APLAYER_VERSION; diff --git a/src/js/template.js b/src/js/template.js index e379f6f..070bbec 100644 --- a/src/js/template.js +++ b/src/js/template.js @@ -33,6 +33,7 @@ class Template { this.time = this.container.querySelector('.aplayer-time'); this.barWrap = this.container.querySelector('.aplayer-bar-wrap'); this.button = this.container.querySelector('.aplayer-button'); + this.body = this.container.querySelector('.aplayer-body'); this.list = this.container.querySelector('.aplayer-list'); this.listOl = this.container.querySelector('.aplayer-list ol'); this.listCurs = this.container.querySelectorAll('.aplayer-list-cur'); @@ -51,6 +52,10 @@ class Template { this.author = this.container.querySelector('.aplayer-author'); this.dtime = this.container.querySelector('.aplayer-dtime'); this.notice = this.container.querySelector('.aplayer-notice'); + this.miniSwitcher = this.container.querySelector('.aplayer-miniswitcher'); + this.skipBackButton = this.container.querySelector('.aplayer-icon-back'); + this.skipForwardButton = this.container.querySelector('.aplayer-icon-forward'); + this.skipPlayButton = this.container.querySelector('.aplayer-icon-play'); } } diff --git a/src/js/utils.js b/src/js/utils.js index ec978f3..152ab87 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -39,7 +39,7 @@ const utils = { return actualLeft - elementScrollLeft; }, - getElementViewTop: (element) => { + getElementViewTop: (element, noScrollTop) => { let actualTop = element.offsetTop; let current = element.offsetParent; let elementScrollTop = 0; @@ -48,7 +48,7 @@ const utils = { current = current.offsetParent; } elementScrollTop = document.body.scrollTop + document.documentElement.scrollTop; - return actualTop - elementScrollTop; + return noScrollTop ? actualTop : actualTop - elementScrollTop; }, isMobile: isMobile, diff --git a/src/template/player.art b/src/template/player.art index c4c2447..fcae1c3 100644 --- a/src/template/player.art +++ b/src/template/player.art @@ -1,3 +1,4 @@ +{{ if !options.fixed }}
{{@ icons.play }}
@@ -25,6 +26,15 @@ 00:00 / 00:00 + + {{@ icons.skip }} + + + {{@ icons.play }} + + + {{@ icons.skip }} +
+
@@ -57,4 +68,76 @@ index: 1 }) }} -
\ No newline at end of file + +{{ else }} +
+ + {{ include './list-item.art' getObject({ + theme: options.theme, + audio: options.audio, + index: 1 + }) }} + +
+
+
+
{{@ icons.play }}
+
+ +
+
+
+
+
+
+{{/if}} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f3523aa..516c155 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6136,6 +6136,10 @@ slide@^1.1.5: version "1.1.6" resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" +smoothscroll@0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/smoothscroll/-/smoothscroll-0.4.0.tgz#40e507b46461408ba1b787d0081e1e883c4124a5" + snapdragon-node@^2.0.1: version "2.1.1" resolved "http://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"