diff --git a/README.md b/README.md index a04954838..749be92aa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # Bilibili-Evolved 增强哔哩哔哩Web端体验的油猴脚本. -(未完成) + +作为[原脚本](bilibili-touch.js)的工程化形式,工程化目前未完成,完成之前可直接以使用原脚本. diff --git a/bilibili-evolved.js b/bilibili-evolved.js index a3004ac0d..a9073f0ec 100644 --- a/bilibili-evolved.js +++ b/bilibili-evolved.js @@ -8,6 +8,7 @@ // @match *://*.bilibili.com // @grant unsafeWindow // @require https://static.hdslb.com/js/jquery.min.js +// @require https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/utils/common.min.js // ==/UserScript== (self$ => @@ -71,386 +72,17 @@ // [New Styles -> Override Nav Bar] // show top banner showBanner: true - // [Not used] - // (Experimental) use dominant color of banner image - // useDominantColor: false }; for (const key in userSettings) { settings[key] = userSettings[key]; } - const $ = unsafeWindow.$ || self$; - const waitForQuery = () => - { - const MaxRetry = settings.maxQueryRetry; - let retry = 0; - const tryQuery = (query, condition, action, failed) => - { - if (retry >= MaxRetry) - { - if (failed) - { - failed(); - } - } - else - { - const result = query(); - if (condition(result)) - { - action(result); - } - else - { - retry++; - setTimeout(() => tryQuery(query, condition, action, failed), settings.queryInterval); - } - } - }; - return tryQuery; - }; const getEventHandler = (element, event) => { return element.data("events")[event][0].handler; }; - const getPosition = element => - { - let x = 0; - let y = 0; - while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) - { - x += element.offsetLeft - element.scrollLeft; - y += element.offsetTop - element.scrollTop; - element = element.offsetParent; - } - return { x: x, y: y }; - }; - class Swiper - { - constructor(element) - { - this.action = new SwipeAction(element); - this.onTouchStart = undefined; - this.onTouchEnd = undefined; - this._direction = null; - - element.addEventListener("touchstart", e => - { - this._xDown = e.touches[0].clientX; - this._yDown = e.touches[0].clientY; - if (this.onTouchStart) - { - this.onTouchStart(e); - } - }); - element.addEventListener("touchmove", e => - { - if (!this._xDown || !this._yDown) - { - return; - } - const xUp = e.touches[0].clientX; - const yUp = e.touches[0].clientY; - const elementPosition = getPosition(element); - const position = { - x: (e.touches[0].pageX - elementPosition.x) / element.clientWidth, - y: (e.touches[0].pageY - elementPosition.y) / element.clientHeight, - width: element.clientWidth, - height: element.clientHeight - }; - - const xDiff = this._xDown - xUp; - const yDiff = this._yDown - yUp; - - if (!this._direction) - { - let direction = ""; - if (Math.abs(xDiff) > Math.abs(yDiff)) - { - direction = "horizontal"; - } - else - { - direction = "vertical"; - } - this._direction = direction; - e.preventDefault(); - } - else - { - if (this._direction === "vertical") - { - this.action.startAction(this._direction, yDiff, position); - } - else if (this._direction === "horizontal") - { - this.action.startAction(this._direction, -xDiff, position); - } - e.preventDefault(); - } - - }); - element.addEventListener("touchend", e => - { - this._xDown = null; - this._yDown = null; - this._direction = null; - if (this.onTouchEnd) - { - this.onTouchEnd(e); - } - }); - } - } - class SwipeAction - { - constructor(element) - { - this.lowSpeedForward = undefined; - this.lowSpeedBackward = undefined; - this.mediumSpeedForward = undefined; - this.mediumSpeedBackward = undefined; - this.highSpeedForward = undefined; - this.highSpeedBackward = undefined; - - this.lowVolumeUp = undefined; - this.lowVolumeDown = undefined; - this.mediumVolumeUp = undefined; - this.mediumVolumeDown = undefined; - this.highVolumeUp = undefined; - this.highVolumeDown = undefined; - - this.speedCancel = undefined; - this.volumeCancel = undefined; - this.minSwipeDistance = 20; - this.onActionStart = undefined; - this.onActionEnd = undefined; - - this._element = element; - this._touchStart = false; - this._startPosition = null; - this._lastAction = null; - element.addEventListener("touchstart", () => - { - this._touchStart = true; - }); - element.addEventListener("touchend", () => - { - this._startPosition = null; - this.onActionEnd && this.onActionEnd(this._lastAction); - this._lastAction = null; - }); - } - - startAction(direction, distance, position) - { - if (this._touchStart) - { - this.onActionStart && this.onActionStart(); - this._startPosition = position; - this._touchStart = false; - } - if (direction === "vertical") - { - if (Math.abs(distance) < this.minSwipeDistance) - { - this.volumeCancel && this.volumeCancel(); - this._lastAction = null; - } - else - { - let volumeFactor = 0; - let upHandler = undefined; - let downHandler = undefined; - if (this._startPosition.x < 1 / 3) - { - volumeFactor = 0.4; - upHandler = this.lowVolumeUp; - downHandler = this.lowVolumeDown; - } - else if (this._startPosition.x >= 1 / 3 && this._startPosition.x <= 2 / 3) - { - volumeFactor = 1; - upHandler = this.mediumVolumeUp; - downHandler = this.mediumVolumeDown; - } - else - { - volumeFactor = 2; - upHandler = this.highVolumeUp; - downHandler = this.highVolumeDown; - } - - if (distance > 0) - { - const volumeChange = Math.round( - volumeFactor * 100 * (distance - this.minSwipeDistance) / (1.5 * position.height) - ); - upHandler && upHandler(volumeChange); - this._lastAction = { - type: "volume", - volume: volumeChange - }; - } - else - { - const volumeChange = Math.round( - volumeFactor * 100 * (distance + this.minSwipeDistance) / (1.5 * position.height) - ); - downHandler && downHandler(volumeChange); - this._lastAction = { - type: "volume", - volume: volumeChange - }; - } - } - } - else if (direction === "horizontal") - { - if (position.y < 1 / 3 && (position.x < 0.1 || position.x > 0.9) || - Math.abs(distance) < this.minSwipeDistance) - { - this.speedCancel && this.speedCancel(); - this._lastAction = null; - } - else - { - let speedFactor = 0; - let forwardHandler = undefined; - let backwardHandler = undefined; - if (this._startPosition.y < 1 / 3) - { - speedFactor = 0.05; - forwardHandler = this.lowSpeedForward; - backwardHandler = this.lowSpeedBackward; - } - else if (this._startPosition.y >= 1 / 3 && this._startPosition.y <= 2 / 3) - { - speedFactor = 0.2; - forwardHandler = this.mediumSpeedForward; - backwardHandler = this.mediumSpeedBackward; - } - else - { - speedFactor = 1; - forwardHandler = this.highSpeedForward; - backwardHandler = this.highSpeedBackward; - } - - if (distance > 0) - { - const seconds = (distance - this.minSwipeDistance) * speedFactor; - forwardHandler && forwardHandler(seconds); - this._lastAction = { - type: "playback", - seconds: seconds - }; - } - else - { - const seconds = (distance + this.minSwipeDistance) * speedFactor; - backwardHandler && backwardHandler(seconds); - this._lastAction = { - type: "playback", - seconds: seconds - }; - } - } - } - } - } - class VideoShot - { - constructor(totalTime) - { - this.interval = totalTime < 5 * 100 ? 5 : totalTime / 100; - this.firstShot = { - css: { - backgroundPosition: "-160px 0px" - }, - x: -160, - y: 0, - position: 2 - }; - this.lastShot = { - css: { - backgroundPosition: "-1440px -810px" - }, - x: -1440, - y: -810, - position: 100 - }; - - this.imageXLength = 10; - this.imageYLength = 10; - this.imageXSize = 160; - this.imageYSize = 90; - this.imageUrl = null; - this.imageIndex = []; - } - init(input) - { - const matches = input.prop("value").match(/aid=([\d]+)&cid=([\d]+)/); - const aid = matches[1]; - const cid = matches[2]; - $.ajax({ - type: "GET", - url: `https://api.bilibili.com/x/player/videoshot?aid=${aid}&cid=${cid}&index=1`, - success: obj => - { - const data = obj.data; - this.imageXLength = data.img_x_len; - this.imageYLength = data.img_y_len; - this.imageXSize = data.img_x_size; - this.imageYSize = data.img_y_size; - this.imageIndex = data.index; - this.imageUrl = data.image[0]; - - this.firstShot = { - css: {}, - x: -this.imageXSize, - y: 0, - position: 1 - }; - this.firstShot.css.backgroundPosition = `${this.firstShot.x}px ${this.firstShot.y}px`; - this.lastShot = { - css: {}, - x: -this.imageXSize * (this.imageXLength - 1), - y: -this.imageYSize * (this.imageYLength - 1), - position: 99 - }; - this.lastShot.css.backgroundPosition = `${this.lastShot.x}px ${this.lastShot.y}px`; - - - } - }); - } - _getStyle(position) - { - if (position <= this.firstShot.position) - { - return this.firstShot.css; - } - else if (position >= this.lastShot.position) - { - return this.lastShot.css; - } - else - { - const x = -160 * (position % 10 - 1); - const y = -90 * (position >= 100 ? 9 : Math.trunc(position / 10)); - return { - backgroundPosition: `${x}px ${y}px` - }; - } - } - getStyle(time) - { - const position = Math.round(time / interval); - return this._getStyle(position); - } - } + $(document).ready(() => { diff --git a/bilibili-touch.js b/bilibili-touch.js new file mode 100644 index 000000000..438304a73 --- /dev/null +++ b/bilibili-touch.js @@ -0,0 +1,3039 @@ +// ==UserScript== +// @name Bilibili Touch +// @namespace http://tampermonkey.net/ +// @version 3.4 +// @description Touch optimizations and UI improvements for Bilibili +// @author Grant Howard +// @match *://*.bilibili.com/* +// @match *://*.bilibili.com +// @grant unsafeWindow +// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAH0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxOC0wMi0yNVQxNDo1NzozOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOC0wMi0yNVQxNDo1NzozOCswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMTgtMDItMjVUMTQ6NTc6MzgrMDg6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODY3MDMzY2UtZjdlMy0wYTRiLWE5YWItODE3ZTI2ZmNlYTMyIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6YWFhN2UzZTQtM2MzOS0yOTQ4LWI1OTgtYTEzM2ZjMTMxNDMyIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6YjRjNGFjZWUtZjQyYS0yMTQwLTlmMzgtY2NlZTc3YmY2ZTM1IiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkhpc3Rvcnk9IjIwMTgtMDItMjVUMTQ6NTc6MTArMDg6MDAmI3g5O+aWh+S7tiDmnKrmoIfpopgtMiDlt7LmiZPlvIAmI3hBO+W7uueriyYjeDk75paw5bu6OiDmlofmoaMmI3hBO0ZBTFNFJiN4QTvmqKHlvI86IFJHQiDpopzoibLmqKHlvI8mI3hBO+WuveW6pjogMi41IOiLseWvuCYjeEE76auY5bqmOiAyLjUg6Iux5a+4JiN4QTvmr4/oi7Hlr7gg5YiG6L6o546HOiA3MiYjeEE75YOP57Sg6ZW/5a695q+UOiAxJiN4QTvloavlhYU6IOmAj+aYjiYjeEE75rex5bqmOiA4JiN4QTvphY3nva7mlofku7Y6IOKAnG5vbmXigJ0mI3hBO+WPguiAg+e6vzog5pegJiN4QTsyMTcmI3hBOyYjeEE757KY6LS0JiN4QTvnspjotLQmI3g5O+a2iOmZpOmUr+m9vzog5pegJiN4QTvkuLo6IOWDj+e0oCYjeEE7JiN4QTsyMDE4LTAyLTI1VDE0OjU3OjM4KzA4OjAwJiN4OTvmlofku7YgQzpcVXNlcnNcVGhlMThcUGljdHVyZXNcR3JhcGhpY3NcYmlsaWJpbGkgbG9nbyBzbWFsbC5wbmcg5bey5a2Y5YKoJiN4QTvlrZjlgqgmI3g5OyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjRjNGFjZWUtZjQyYS0yMTQwLTlmMzgtY2NlZTc3YmY2ZTM1IiBzdEV2dDp3aGVuPSIyMDE4LTAyLTI1VDE0OjU3OjM4KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo4NjcwMzNjZS1mN2UzLTBhNGItYTlhYi04MTdlMjZmY2VhMzIiIHN0RXZ0OndoZW49IjIwMTgtMDItMjVUMTQ6NTc6MzgrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+5IvsDQAAD/9JREFUeJztnX+MFdUVxz+6BHSJIC0VZFuXUBHBVbYSaaDBdJttILVq/YH1Z9VAJZVqpWol0mg0SiD4YyNFqtH0h6hUUxq1GEhpNJpCagvdKhWrFqEURMWgS/fhNov0jzOT93z7fsydufPrvvNJJixvZ+6cfe879517zrn3HnH48GEUxRWOTNsARbGJClpxChW04hQqaMUpVNCKU6igFadQQStOoYJWnEIFrTiFClpxChW04hQqaMUpBgU56eDBgwAM3XB0rMZYYjTQCbQA+4HdwNpULcoGHcBkYAjynmwGtqVqkQGfnh3svECChtyIuRO4FfnwfPqAjcBPvX8bjVbgPmA68rD7dAP3AqtSsCk2XHI5OoAH+KyYQXqkDuAJ4OKkjUqZDuAx4Hw+K2aAduAu4KaEbYoVlwT9Q2Bijd+3AsuRD7cRmAIsA2bUOKcVxx5yVwTdzsCeuRIjgTsCnptnJgIPIaKuxzjEVXMCVwQ9GBgR8Nw2xKd05kMsYwqwgmBiBnnfWuMzJ1lcEfR7hue3I77lOfZNSZXxwBLMv4GOicGWVHBF0DuB9YbXjEbcj2X2zUmFicAvMf/mOQR02TYmLVwRNMDdQMHwmnakN6s1cMoDHcAtSGjOhP3AIvvmpEdgQfd2HozTDhu8DCxEehwTpgBPAZdbtygZWpEB4JUhru0Cllq1JmVc6qFBwnKXhLhuNOJ75k3Ufpx5fIhrFwN32jUnfYwEnYNeGuBp5MMypQWJDsy2a05sTEGiNWHcpaU45mr4uNZD+ywCFoS4bhjwINlPvvhx5vYQ1y5GXDMnMRZ0TnppEP9wEbDP8LqRSM+XVVGbxpl99iMuxm3WLcoQoXroHIl6MeGKb1oR0WQt+RI2zgzyPtyO+aA5V7jqcpSyAFiJlEyaMBq4n+zEqdsRUYZ5yObhsJtRSihB56SUtJRrkTh1n+F1bcAswvmqNpkO/ACYanhdAXkIVmEeo88lRwRZffTI5xKwJBluQHpdU/YD15NO7XAL8AdqVxJWo4twg+PMEbTAvxFcjlK6ED/SlBFIVCHpUssZSDo7jJgX44iYTWg0QYOM9C8KcV0zEiFIKvoxBbiHcD7zwziYNAlCIwoaJPlyc4jrJiIzX66xa07F+zyCuc8M0jPPw3y84ASNKmiQNPnyENcNQeYtxhXS80Nz7SGu3YC4KA1L4EmyMXE+Uosbd2y0B/hCyb0KwCjMkxM+rcA64EngWSTDCNDv/Wv69/R717QhvX/5/L+g9AFzkGTSf7w2h5XYVWpbk/dzH/KQRqEZeU/3Ig9VaiQZ5RiPJAQ6ka/U0chgq8lK60oW2AscQKJC3UiN+makXj0SQaMccQu6CYkBdyKCDjNaV/LNVuRbbB1S4hsK6+tyhGAqMkU+L9VrSjy0ecds4FFgNRZ67GrEMSj0B01rUTErRfzB7m+Jse7ctqD9sNbdSNWaopQzBYnELCH6YHQANl2OmUhaWf1kpR5NyBzIJmSygWmJb1Vs9dAqZiUMNyH1MWGmkFXElqBVzEpYZgLnYsn9iCroqcC7qJiVaCxDxl6RiSroOYTPailKKbOwEP2IIugbiL9IR2kcmpHlfdujNBJW0OOB66LcWFEq0IpEP0ITVtALkGVYFcU2FxOhkjGMoNuAy8LeUFECENqVDSPoayiWSypKHMwm5HQ300zhROJZU3ktUna4F/jA+7kfsc+v2y2ntOy0Xv1x1Bhn1Nkfcd/fegq5jCbPBv99HoKU/o5AaszjqM2YjRQyGWEq6Buxu9p7AckWrbTYppI8/lp5NicRdyAPitFMe1OXw2YCZTMyY0XFnH+2IlGv5dibfTSCEIEHkx56OuYLaldjHzAXmdWguME+ZO2SAhFDbyUYTxI26aFnmjZehR7k66nbUntKtrgTWUbBBm0YdqImgrbVOz+OvT9YyR4FZGaKjZLQVgx76aCCHoI8LTZ4yVI7SnZ5BXjNUltG47aggp6CnSKkrcDzFtpRso9xyK0KRrXSQQVtK7rxLOJDK+6zGnjBQjtGSbyggm4JYUglNltqR8k+PdhZwtdIe0EFbWsxmP76pygOYSMmbZT8CypoW5NpG2LRbcUqzSYnBxX04BCGVMLp/T2UAdhYAXUIBuWkSffQ6nIopjRhMDBM2odOe7VTJZ8EdjuCCtrW4tm2XBclH9jqCK0LWlHCkPg3skY5lDixNfEgcDBBe2jFKVTQilOooBWnUEErTqGCVpxCEx0DGY5sSQzw+zQNqUIHcBrwD1LeQi2LNLKgj0JmFY8CLvD+fwIy5Wc4sBu4hAg7N8XAUOAO5IHrB15EljPuBXYhIt/i/dyQNJKgxwEnAdOA04FJ1J4m34IsF5wlQZ9O8dtjEJWLdt4HXgX+DryNLNzzsve687gq6DOQr+XTgDGIEIYDnzdsZ4xlu6JyfIBzjkOEXir2HUhP/gbSi/uHcz153gU9FDgW+A4i2HHeMQlxJaIywUIbNjkl5HVjvWNayWs7EIHvAv6JCP4dYFNo6zJA3gR9EtL7TkOWVfhKzPc7wbvfX2K+T1DCCroSY72jlF7EB/d78h2I0LeQk9LfrAv6MsSX/TEyaBuegg2TyIagB2FX0JUYivjoM8pefxfxybcgkZ/M9uJZFvRPkA0807ZxUsr39zkNODmlex/vHbOQYvvMCjrLiZUW0hczZEfQk9M2wONLaRtQiywLOrYNzg2xMbi0QVaElOnF7rMs6MfTNsDjDOIffAZhVtoGeDyTtgG1yLKgP/aOLPBh2gYgEYgssCNtA2qRZUF/AryZsg39wL/JRpZtI+mL+n0kZp1ZsjDoqsUW5Cs/CXqB15G08eve8RpS05EFbgeeQnzpE5GoRxuS/PlcQjb8C4lRZ5asC/pvMbX7McUlX3ci3wR7EDFnGT9lXcopSEjtOETck5FUfxyDyLdjaNMqWRe0jXhnL7IK5ofAdoo9cNrujC0qifxkROhfRIQ+GckKRk3MqKAj8ibiSx8V4Nx+ii7CfylWnb1JtirmkuANBroGxyNC9xM0E5BefBgi+iC8Y8vAuMi6oD9BetbyJVU/QQZr273fv+od6xK1Ll+86x3lazafApyL+OG+fz4BSYOXoz20BWYD91J0GV5BfOvX0zTKISq5LNOAMym6LX3AGjKc8vbJg6A3YW/DIiUYm8iBeCuR5Ti0ohijglacQgWtOIUKWnEKFbTiFCpoxSlU0IpTqKAVp1BBK06hglacQgWtOIUKWnEKFbTiFCpoxSlU0PFzNbAaKZxXYiYP9dB5pgO4CVlO7ADw/XTNcR/toePlCopr480F5qVoS0Oggo6Pq4Hvlb32Xcx3EVAMUEHX5lvAz4GfIe5DUCYBtwJNZa93IEsEm3Ae8CdgEfow1EV96MqMAa4FFgDN3mujkFnP9fYlORYRX7VB4DyvnXsC2DEfeTDGIPMqZwBPAL8OcG1DooIeyHTgNmBm2esXAh8BK4DuKtceA9wFXFrnHvORdUNqCXMRcIvXps9Mz76xwJ117tGQqMsxkDEMFLPPXMQFOa/C7yYA9yFircdYZL/B+XxWsHj/ryTm0t9X2s5NQXvoSmwEngPOrvL7ryJx5dXAZiQcdyoislMN7jMW8c3nIj31/xAfeQb1Bas7yFZBBT2QPYhb8WWqb0cxGIlglEcxwtDuHUF5BHjAwn2dRF2OyqwHfoS91ZkOWGrnOWAh4ssrFVBBV2cDcDPVB4BBeBtYCnwT+EVEe1YgmcYs7CaQWdTlqM3ziAuyBPGdjzW49gXgfqRXBVn5/gAyECyPT9fiPWA5sAzxs5Ua5FbQvZ0HARi64ei4b9WNbNgzFwnHnUltQe4CfoNsetRd8vpHiBuzHvG9L6zTTgF4DFhL8aFQ6pBbQafAI94xH4kFT0IGjscg61LvAf6K9Ooba7TzvHdcikQzTqS4c+suZFOeV4E/Ar+z/Dc4T+4F3dt5MIleupQV3uEzCnElCobtPOEdILHvA9gbPDYsSQ8Kh8TRqO9+pMR7mIu5nD24KeY+S+30Bz0xaUHb+gMH0Nt5MG1hKxkgqKADPyGKUoIt3RwKemJQQQduMKF2qqK9tJNYdzlsCdEk/hoadT+cI/D4Iukoh7VBYcKRDSUcNvTVB3wQ9GQnohxKZrHxjdyH7HwWiKQHhYm4HEpmsNFDG7m7QQW9LYQhldDC9MahFTjBQjs7TU4OKug3iJ48ADgfGG2hHSX7fB2YaKGd8i2eaxJU0N3AVmNTBtICnGWhHSX7zLbUTrfJySaDQqOGa3COpXaU7OLPULfBWyYnpyXoWyy1pWSPFuBGYJiFtgrI/u6BMRH0i9hLsNyG2cItSn64ARkr2WAzMboc27A327gZeAiYA4yw1KaSLhORybtzLLZprDfTxIqt8B3AeKRgfgka+cg7nchneR32Oqi3kB7aCNPA98PA5cBI0xvV4BpgKpKv34+kOQ9QTOYMon5ip97fkfb19Ui7/SAcQrJ2Q5Bv2BElx3QL7ZezBpl+ZoTpG7UNWIX4STZpt9yekm8KyCRjY8LUcnRhJ8miKNVYhUwmNiaMoHcCD4a5maIE4C1gZdiLw1bbdVF7ZrOihOVJIuQ8wgp6NxGeIkWpwlbgV1EaiFIPvYpgi3YrShC2I8sIG2UGy4la4N+Fuh6KHdYAz0ZtJKqgdwNfA16OaojS0FwF3G6jIVtTsBYSMsyiNDQ9iM/8NJZCwbYEvRERdeC5X4oC3Iv0ztbyGjYnyXYDl6DRD6U+25EdxqxvfGR71vd2ZDu0a7FbyKS4wwbgIiSgYJ24ljFYCVyAFDPtj+keSr7oRtzSqwhRRReUIw4fPlz3pCOjLbfdicwva0NWwdelDBqHnUgqez3wDIbTqUr5tNqeZGUksXLSBoqF2lcipYajvWMcA0tRDxG/6P2ZN/596g1Ksv4Qln6OSdha/v4dAvZ6RwEpAd6KfO6JhnST6KFr0YbU0zYj+4cMBoYi4Zw+4CRkk0sbM8XXInuVFBAB9Hiv+/W9Luz9N5Pi+1dAJlGcg50pUWsQv7cZmS+4j+J7OIKY37+gPXTagg5CE/LGNdc7sQb7gW/TmFlNG+9fD/ANYvR96xFU0HnY1u0QEfP7XhuNKGaQv323hTZSE7MJeRA0wJ8jXv+SFSvyS6jZHyXkJmGWF0HfS/heugdLdQI5Zinhe9iCd30uyIugtwGLQ1zXh9QK2FjGLM9sJ1wGtw8ZDEbt4RMjL4IGeBRJlwZdjbKAjMqvj8ugnPEoZj1tj3f+FfGYEw9526ewCxmg3ErttTz2IX7zwgRsyhMLkffvYiQHUI29SG1y7ly1PITtKjEOia3ORJZAGIn0KJuQr8e1qJtRi3FIfPosiu/fPiQStA7J7EWNLFnFpTh0PfxtLmLbA9FxSrN9mcWqoBUlL+RpUKgodVFBK06hglacQgWtOIUKWnEKFbTiFCpoxSlU0IpTqKAVp1BBK06hglacQgWtOIUKWnGK/wNSsuaxzUMuHQAAAABJRU5ErkJggg== +// @require https://static.hdslb.com/js/jquery.min.js +// @require https://raw.githubusercontent.com/briangonzalez/rgbaster.js/master/rgbaster.min.js +// ==/UserScript== + +(self$ => +{ + const colors = { + red: "#E53935", + pink: "#F06292", + purple: "#AB47BC", + deepPurple: "#7E57C2", + indigo: "#7986CB", + blue: "#1E88E5", + lightBlue: "#00A0D8", + cyan: "#00ACC1", + teal: "#26A69A", + green: "#66BB6A", + lightGreen: "#8BC34A", + lime: "#CDDC39", + yellow: "#FFEB3B", + amber: "#FFC107", + orange: "#FF9800", + deepOrange: "#FF5722", + brown: "#795548", + grey: "#757575", + blueGrey: "#607D8B" + }; + // User settings will overwrite default settings below + const userSettings = { + customStyleColor: colors.pink, + showBanner: false, + useDarkMode: true + }; + const settings = { + // remove ads + removeAds: true, + // max retry count used for query elements + maxQueryRetry: 30, + // query retry interval (ms) + queryInterval: 500, + // touch support for nav bar (not compatible with Edge) + touchNavBar: true, + // (Experimental) touch support for video player + touchVideoPlayer: true, + // redirect to original sites in watchlater list + watchLaterRedirect: true, + // auto expand danmaku list + expandDanmakuList: true, + // use new styles for nav bar and player + useNewStyles: true, + // [New Styles] + // set theme color (must in #rrggbb format, not compatible with Edge) + customStyleColor: colors.pink, + // [New Styles] + // set background blur opacity of nav bar + blurBackgroundOpacity: 0.382, + // [New Styles] + // (Not Implemented) use dark mode + useDarkMode: true, + // [New Styles] + // (Experimental) use new nav bar in old sites + overrideNavBar: true, + // [New Styles -> Override Nav Bar] + // show top banner + showBanner: true + // [Not used] + // (Experimental) use dominant color of banner image + // useDominantColor: false + }; + for (const key in userSettings) + { + settings[key] = userSettings[key]; + } + + const $ = unsafeWindow.$ || self$; + const waitForQuery = () => + { + const MaxRetry = settings.maxQueryRetry; + let retry = 0; + const tryQuery = (query, condition, action, failed) => + { + if (retry >= MaxRetry) + { + if (failed) + { + failed(); + } + } + else + { + const result = query(); + if (condition(result)) + { + action(result); + } + else + { + retry++; + setTimeout(() => tryQuery(query, condition, action, failed), settings.queryInterval); + } + } + }; + return tryQuery; + }; + const getEventHandler = (element, event) => + { + return element.data("events")[event][0].handler; + }; + const getPosition = element => + { + let x = 0; + let y = 0; + while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) + { + x += element.offsetLeft - element.scrollLeft; + y += element.offsetTop - element.scrollTop; + element = element.offsetParent; + } + return { x: x, y: y }; + }; + class Swiper + { + constructor(element) + { + this.action = new SwipeAction(element); + this.onTouchStart = undefined; + this.onTouchEnd = undefined; + this._direction = null; + + element.addEventListener("touchstart", e => + { + this._xDown = e.touches[0].clientX; + this._yDown = e.touches[0].clientY; + if (this.onTouchStart) + { + this.onTouchStart(e); + } + }); + element.addEventListener("touchmove", e => + { + if (!this._xDown || !this._yDown) + { + return; + } + const xUp = e.touches[0].clientX; + const yUp = e.touches[0].clientY; + const elementPosition = getPosition(element); + const position = { + x: (e.touches[0].pageX - elementPosition.x) / element.clientWidth, + y: (e.touches[0].pageY - elementPosition.y) / element.clientHeight, + width: element.clientWidth, + height: element.clientHeight + }; + + const xDiff = this._xDown - xUp; + const yDiff = this._yDown - yUp; + + if (!this._direction) + { + let direction = ""; + if (Math.abs(xDiff) > Math.abs(yDiff)) + { + direction = "horizontal"; + } + else + { + direction = "vertical"; + } + this._direction = direction; + e.preventDefault(); + } + else + { + if (this._direction === "vertical") + { + this.action.startAction(this._direction, yDiff, position); + } + else if (this._direction === "horizontal") + { + this.action.startAction(this._direction, -xDiff, position); + } + e.preventDefault(); + } + + }); + element.addEventListener("touchend", e => + { + this._xDown = null; + this._yDown = null; + this._direction = null; + if (this.onTouchEnd) + { + this.onTouchEnd(e); + } + }); + } + } + class SwipeAction + { + constructor(element) + { + this.lowSpeedForward = undefined; + this.lowSpeedBackward = undefined; + this.mediumSpeedForward = undefined; + this.mediumSpeedBackward = undefined; + this.highSpeedForward = undefined; + this.highSpeedBackward = undefined; + + this.lowVolumeUp = undefined; + this.lowVolumeDown = undefined; + this.mediumVolumeUp = undefined; + this.mediumVolumeDown = undefined; + this.highVolumeUp = undefined; + this.highVolumeDown = undefined; + + this.speedCancel = undefined; + this.volumeCancel = undefined; + this.minSwipeDistance = 20; + this.onActionStart = undefined; + this.onActionEnd = undefined; + + this._element = element; + this._touchStart = false; + this._startPosition = null; + this._lastAction = null; + element.addEventListener("touchstart", () => + { + this._touchStart = true; + }); + element.addEventListener("touchend", () => + { + this._startPosition = null; + this.onActionEnd && this.onActionEnd(this._lastAction); + this._lastAction = null; + }); + } + + startAction(direction, distance, position) + { + if (this._touchStart) + { + this.onActionStart && this.onActionStart(); + this._startPosition = position; + this._touchStart = false; + } + if (direction === "vertical") + { + if (Math.abs(distance) < this.minSwipeDistance) + { + this.volumeCancel && this.volumeCancel(); + this._lastAction = null; + } + else + { + let volumeFactor = 0; + let upHandler = undefined; + let downHandler = undefined; + if (this._startPosition.x < 1 / 3) + { + volumeFactor = 0.4; + upHandler = this.lowVolumeUp; + downHandler = this.lowVolumeDown; + } + else if (this._startPosition.x >= 1 / 3 && this._startPosition.x <= 2 / 3) + { + volumeFactor = 1; + upHandler = this.mediumVolumeUp; + downHandler = this.mediumVolumeDown; + } + else + { + volumeFactor = 2; + upHandler = this.highVolumeUp; + downHandler = this.highVolumeDown; + } + + if (distance > 0) + { + const volumeChange = Math.round( + volumeFactor * 100 * (distance - this.minSwipeDistance) / (1.5 * position.height) + ); + upHandler && upHandler(volumeChange); + this._lastAction = { + type: "volume", + volume: volumeChange + }; + } + else + { + const volumeChange = Math.round( + volumeFactor * 100 * (distance + this.minSwipeDistance) / (1.5 * position.height) + ); + downHandler && downHandler(volumeChange); + this._lastAction = { + type: "volume", + volume: volumeChange + }; + } + } + } + else if (direction === "horizontal") + { + if (position.y < 1 / 3 && (position.x < 0.1 || position.x > 0.9) || + Math.abs(distance) < this.minSwipeDistance) + { + this.speedCancel && this.speedCancel(); + this._lastAction = null; + } + else + { + let speedFactor = 0; + let forwardHandler = undefined; + let backwardHandler = undefined; + if (this._startPosition.y < 1 / 3) + { + speedFactor = 0.05; + forwardHandler = this.lowSpeedForward; + backwardHandler = this.lowSpeedBackward; + } + else if (this._startPosition.y >= 1 / 3 && this._startPosition.y <= 2 / 3) + { + speedFactor = 0.2; + forwardHandler = this.mediumSpeedForward; + backwardHandler = this.mediumSpeedBackward; + } + else + { + speedFactor = 1; + forwardHandler = this.highSpeedForward; + backwardHandler = this.highSpeedBackward; + } + + if (distance > 0) + { + const seconds = (distance - this.minSwipeDistance) * speedFactor; + forwardHandler && forwardHandler(seconds); + this._lastAction = { + type: "playback", + seconds: seconds + }; + } + else + { + const seconds = (distance + this.minSwipeDistance) * speedFactor; + backwardHandler && backwardHandler(seconds); + this._lastAction = { + type: "playback", + seconds: seconds + }; + } + } + } + } + } + class VideoShot + { + constructor(totalTime) + { + this.interval = totalTime < 5 * 100 ? 5 : totalTime / 100; + this.firstShot = { + css: { + backgroundPosition: "-160px 0px" + }, + x: -160, + y: 0, + position: 2 + }; + this.lastShot = { + css: { + backgroundPosition: "-1440px -810px" + }, + x: -1440, + y: -810, + position: 100 + }; + + this.imageXLength = 10; + this.imageYLength = 10; + this.imageXSize = 160; + this.imageYSize = 90; + this.imageUrl = null; + this.imageIndex = []; + } + init(input) + { + const matches = input.prop("value").match(/aid=([\d]+)&cid=([\d]+)/); + const aid = matches[1]; + const cid = matches[2]; + $.ajax({ + type: "GET", + url: `https://api.bilibili.com/x/player/videoshot?aid=${aid}&cid=${cid}&index=1`, + success: obj => + { + const data = obj.data; + this.imageXLength = data.img_x_len; + this.imageYLength = data.img_y_len; + this.imageXSize = data.img_x_size; + this.imageYSize = data.img_y_size; + this.imageIndex = data.index; + this.imageUrl = data.image[0]; + + this.firstShot = { + css: {}, + x: -this.imageXSize, + y: 0, + position: 1 + }; + this.firstShot.css.backgroundPosition = `${this.firstShot.x}px ${this.firstShot.y}px`; + this.lastShot = { + css: {}, + x: -this.imageXSize * (this.imageXLength - 1), + y: -this.imageYSize * (this.imageYLength - 1), + position: 99 + }; + this.lastShot.css.backgroundPosition = `${this.lastShot.x}px ${this.lastShot.y}px`; + + + } + }); + } + _getStyle(position) + { + if (position <= this.firstShot.position) + { + return this.firstShot.css; + } + else if (position >= this.lastShot.position) + { + return this.lastShot.css; + } + else + { + const x = -160 * (position % 10 - 1); + const y = -90 * (position >= 100 ? 9 : Math.trunc(position / 10)); + return { + backgroundPosition: `${x}px ${y}px` + }; + } + } + getStyle(time) + { + const position = Math.round(time / interval); + return this._getStyle(position); + } + } + + $(document).ready(() => + { + // Remove ads + if (settings.removeAds) + { + waitForQuery()( + () => $("#slide_ad"), + it => it.length > 0, + it => it.css("display", "none") + ); + waitForQuery()( + () => $("#home_popularize"), + it => it.length > 0, + it => it.css("display", "none") + ); + waitForQuery()( + () => $(".gg-floor-module"), + it => it.length > 0, + it => it.css("display", "none") + ); + } + + // Touch nav bar + if (settings.touchNavBar) + { + waitForQuery()( + () => $("ul.fr>li.nav-item").not(".profile-info"), + it => it.length === 6, + navItems => + { + const navTouch = (_, nav) => + { + const $nav = $(nav); + $nav.css("cursor", "pointer"); + const a = $nav.find("a.t"); + a.removeAttr("href"); + }; + navItems.each(navTouch); + } + ); + } + + // Touch video player + const touchVideoPlayer = () => + { + waitForQuery()( + () => $(".bilibili-player-video-web-fullscreen"), + it => it.length > 0, + fullscreenButton => + { + if (!fullscreenButton.hasClass("bilibili-player-video-btn") && + $(".bilibili-player-video-btn-fullscreen").data("events")) + { + const clickHandler = getEventHandler( + $(".bilibili-player-video-btn-fullscreen"), "click"); + fullscreenButton + .detach() + .insertAfter(".bilibili-player-video-btn-widescreen") + .addClass("bilibili-player-video-btn") + .on("click", clickHandler); + } + } + ); + waitForQuery()( + () => $(".bilibili-player-iconfont,.bilibili-player-video-quality-menu"), + it => it.length > 0, + icons => icons.unbind("click") + ); + waitForQuery()( + () => $(".bilibili-player-video"), + it => it.length > 0 && $("video").length > 0, + player => + { + if ($(".touch-video-box").length === 0) + { + $(".bilibili-player-video-subtitle").before(`
+
+
+
+
`); + const swiper = new Swiper(player.get(0)); + const text = document.getElementsByClassName("touch-video-info")[0]; + const box = document.getElementsByClassName("touch-video-box")[0]; + swiper.action.speedCancel = () => + { + text.innerHTML = `松开手指,取消进退`; + }; + swiper.action.volumeCancel = () => + { + text.innerHTML = `松开手指,取消调整`; + }; + swiper.action.onActionStart = () => + { + box.style.display = "flex"; + text.innerHTML = ""; + }; + + const fixed = (number, precision = 1) => + { + const str = number.toString(); + const index = str.indexOf("."); + if (index !== -1) + { + if (str.length - index > precision + 1) + { + return str.substring(0, index + precision + 1); + } + else + { + return str; + } + } + else + { + return str + ".0"; + } + }; + const secondsToTime = sec => + { + sec = Math.abs(sec); + const hours = Math.floor(sec / 3600); + const minutes = Math.floor((sec - hours * 3600) / 60); + const seconds = sec - hours * 3600 - minutes * 60; + + let result = fixed(seconds) + "秒"; + if (minutes > 0) + { + result = minutes + "分" + result; + } + if (hours > 0) + { + result = hours + "小时" + result; + } + + return result; + }; + const secondsToHms = sec => + { + sec = Math.abs(sec); + const hours = Math.floor(sec / 3600); + const minutes = Math.floor((sec - hours * 3600) / 60); + const seconds = sec - hours * 3600 - minutes * 60; + + let result = (seconds < 10 ? "0" : "") + fixed(seconds); + result = (minutes < 10 ? "0" : "") + minutes + ":" + result; + result = (hours < 10 ? "0" : "") + hours + ":" + result; + + return result; + }; + + const video = $("video"); + const videoDuration = video.prop("duration"); + //const videoshot = new VideoShot(videoDuration); + const speedChange = speed => + { + return sec => + { + const current = video.prop("currentTime"); + let info = `
${speed}速进度: ${sec > 0 ? "+" : "-"}`; + const commonInfoPart = `
`; + const finalTime = current + sec; + if (finalTime > videoDuration) + { + info += `${secondsToTime(videoDuration - current)}${commonInfoPart}${secondsToHms(current)} → ${secondsToHms(videoDuration)} (100%)`; + } + else if (finalTime < 0) + { + info += `${secondsToTime(current)}${commonInfoPart}${secondsToHms(current)} → ${secondsToHms(0)} (0%)`; + } + else + { + info += `${secondsToTime(sec)}${commonInfoPart}${secondsToHms(current)} → ${secondsToHms(finalTime)} (${fixed(100 * finalTime / videoDuration)}%)`; + } + text.innerHTML = info + `
`; + //$(".videoshot").css(videoshot.getStyle(finalTime)); + }; + }; + swiper.action.lowSpeedBackward = speedChange("低"); + swiper.action.lowSpeedForward = speedChange("低"); + swiper.action.mediumSpeedBackward = speedChange("中"); + swiper.action.mediumSpeedForward = speedChange("中"); + swiper.action.highSpeedBackward = speedChange("高"); + swiper.action.highSpeedForward = speedChange("高"); + + const volumeChange = speed => + { + return volume => + { + const current = Math.round(video.prop("volume") * 100); + let info = `
${speed}速音量: ${volume > 0 ? "+" : "-"}`; + const commonInfoPart = `
`; + const finalVolume = current + volume; + if (finalVolume > 100) + { + info += `${100 - current}${commonInfoPart}${current} → 100`; + } + else if (finalVolume < 0) + { + info += `${current}${commonInfoPart}${current} → 0`; + } + else + { + info += `${Math.abs(volume)}${commonInfoPart}${current} → ${finalVolume}`; + } + text.innerHTML = info + `
`; + }; + }; + swiper.action.lowVolumeUp = volumeChange("低"); + swiper.action.lowVolumeDown = volumeChange("低"); + swiper.action.mediumVolumeUp = volumeChange("中"); + swiper.action.mediumVolumeDown = volumeChange("中"); + swiper.action.highVolumeUp = volumeChange("高"); + swiper.action.highVolumeDown = volumeChange("高"); + + swiper.action.onActionEnd = action => + { + box.style.display = "none"; + text.innerHTML = ""; + if (action) + { + if (action.type === "volume") + { + let volume = video.prop("volume"); + volume += action.volume / 100; + if (volume < 0) + { + volume = 0; + } + else if (volume > 1) + { + volume = 1; + } + video.prop("volume", volume); + $(".bilibili-player-video-volume-num").text(Math.round(volume * 100)); + $(".bpui-slider-progress").css("height", volume * 100 + "%"); + $(".bpui-slider-handle").css("bottom", (35 + volume * 230) / 3 + "%"); + + if (volume === 0) + { + $(".icon-24soundoff").show(); + $(".icon-24soundlarge").hide(); + $(".icon-24soundsmall").hide(); + } + else if (volume >= 0.5) + { + $(".icon-24soundoff").hide(); + $(".icon-24soundlarge").show(); + $(".icon-24soundsmall").hide(); + } + else + { + $(".icon-24soundoff").hide(); + $(".icon-24soundlarge").hide(); + $(".icon-24soundsmall").show(); + } + } + else if (action.type === "playback") + { + let time = video.prop("currentTime"); + time += action.seconds; + if (time < 0) + { + time = 0; + } + else if (time > videoDuration) + { + time = videoDuration; + } + video.prop("currentTime", time); + } + } + }; + } + } + ); + + if ($("#bilibili-touch-video-player").length === 0) + { + const videoPlayerStyles = ``; + $("body").after(videoPlayerStyles); + } + // waitForQuery()( + // () => $(".bilibili-player-video-progress-detail-img"), + // it => it.length > 0, + // videoshot => + // { + + // } + // ); + + }; + touchVideoPlayer(); + + // Watch later redirect + const watchlaterRedirect = () => + { + if (settings.watchLaterRedirect) + { + const redirectLinks = items => + { + if (items.attr("href").indexOf("watchlater") !== 0) + { + const watchlaterList = items + .map((_, it) => "https://www.bilibili.com/" + $(it) + .attr("href") + .match(/av[\d]+/)[0]); + items.each((index, it) => $(it).attr("href", watchlaterList[index]).attr("target", "_blank")); + } + }; + waitForQuery()( + () => $(".av-item>a"), + it => it.length > 0, + items => redirectLinks(items) + ); + waitForQuery()( + () => $("div.watch-later-m>ul>div>li>a"), + it => it.length > 0, + items => redirectLinks(items) + ); + waitForQuery()( + () => $(".read-more.mr"), + it => it.length > 0, + it => it.remove() + ); + waitForQuery()( + () => $(".read-more-grp>.read-more"), + it => it.length > 0, + it => it.css({ + float: "none", + width: "auto" + }) + ); + if (document.URL.indexOf("watchlater") !== -1 && document.URL.match(/av[\d]+/)) + { + const av = document.URL.match(/av[\d]+/)[0]; + if (av) + { + document.URL.replace(`https://www.bilibili.com/${av}`); + } + } + } + }; + watchlaterRedirect(); + + // Expand danmaku list + const expandDanmakuList = () => + { + if (settings.expandDanmakuList) + { + waitForQuery()( + () => $(".bui-collapse-header"), + it => it.length > 0, + button => + { + if (parseInt($(".bui-collapse-body").css("height")) === 0 && + $(".bui-collapse-arrow-text").text() === "展开") + { + button.click(); + } + } + ); + } + }; + expandDanmakuList(); + + $(document).ajaxComplete(() => + { + expandDanmakuList(); + watchlaterRedirect(); + touchVideoPlayer(); + }); + + // Styles + if (settings.useNewStyles) + { + waitForQuery()( + () => $(".nav-search-keyword"), + it => it.length > 0, + textBox => textBox.attr("placeholder", "搜索") + ); + waitForQuery()( + () => $(".custom-scrollbar"), + it => it.length > 0, + it => it.removeClass("custom-scrollbar") + ); + // waitForQuery()( + // () => $("li.home>a"), + // it => it.length > 0 && it.css("font-size").indexOf("12px") !== -1, + // it => it.css("font-size", "0px") + // ); + + const getForeground = () => + { + const regex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(settings.customStyleColor); + const color = regex ? { + r: parseInt(regex[1], 16), + g: parseInt(regex[2], 16), + b: parseInt(regex[3], 16) + } : undefined; + if (color) + { + const grey = 1 - (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255; + if (grey < 0.35) + { + return "#000"; + } + else + { + return "#fff"; + } + } + else + { + return "#fff"; + } + }; + + const updateDarkModeColor = () => + { + if (settings.useDarkMode) + { + const foreground = getForeground(); + const darkStyles = ``; + $("#bilibili-new-style-dark").remove(); + $("body").after(darkStyles); + } + }; + const updateStyleColor = () => + { + const foreground = getForeground(); + const navbar = document.getElementsByClassName("bili-wrapper")[0]; + let useFlexStyles = false; + if (navbar instanceof Element) + { + useFlexStyles = parseInt(window.getComputedStyle(navbar).height) === 50; + } + let styles = ""; + if (useFlexStyles) + { + styles = ``; + } + else + { + styles = ``; + + + if (settings.overrideNavBar) + { + waitForQuery()( + () => $(".search"), + it => it.length > 0 && $(".nav-con.fr").length > 0, + textBox => + { + textBox.detach() + .insertAfter(".nav-con.fr"); + } + ); + waitForQuery()( + () => $("input.search-keyword"), + it => it.length > 0, + textBox => textBox.attr("placeholder", "搜索") + ); + const navBarStyles = ``; + $("body").after(navBarStyles); + + if (!settings.showBanner) + { + const bannerStyles = ``; + $("body").after(bannerStyles); + } + } + } + $("#bilibili-new-style").remove(); + $("body").after(styles); + }; + const updateColors = () => + { + updateDarkModeColor(); + updateStyleColor(); + }; + updateColors(); + + // if (settings.useDominantColor && !(settings.overrideNavBar && !settings.showBanner)) + // { + // waitForQuery()( + // () => $("#banner_link"), + // it => it.length > 0 && it.css("background-image") !== "none", + // image => + // { + // const src = image.css("background-image").match(/url\("(.+)"\)/)[1]; + // RGBaster.colors(src, { + // success: color => + // { + // settings.customStyleColor = color.dominant; + // updateColors(); + // } + // }); + // } + // ); + // } + } + + }); +})(window.jQuery.noConflict(true)); diff --git a/touch/touch-player.js b/touch/touch-player.js index e69de29bb..eed996454 100644 --- a/touch/touch-player.js +++ b/touch/touch-player.js @@ -0,0 +1,246 @@ +const getPosition = element => +{ + let x = 0; + let y = 0; + while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) + { + x += element.offsetLeft - element.scrollLeft; + y += element.offsetTop - element.scrollTop; + element = element.offsetParent; + } + return { x: x, y: y }; +}; +class Swiper +{ + constructor(element) + { + this.action = new SwipeAction(element); + this.onTouchStart = undefined; + this.onTouchEnd = undefined; + this._direction = null; + + element.addEventListener("touchstart", e => + { + this._xDown = e.touches[0].clientX; + this._yDown = e.touches[0].clientY; + if (this.onTouchStart) + { + this.onTouchStart(e); + } + }); + element.addEventListener("touchmove", e => + { + if (!this._xDown || !this._yDown) + { + return; + } + const xUp = e.touches[0].clientX; + const yUp = e.touches[0].clientY; + const elementPosition = getPosition(element); + const position = { + x: (e.touches[0].pageX - elementPosition.x) / element.clientWidth, + y: (e.touches[0].pageY - elementPosition.y) / element.clientHeight, + width: element.clientWidth, + height: element.clientHeight + }; + + const xDiff = this._xDown - xUp; + const yDiff = this._yDown - yUp; + + if (!this._direction) + { + let direction = ""; + if (Math.abs(xDiff) > Math.abs(yDiff)) + { + direction = "horizontal"; + } + else + { + direction = "vertical"; + } + this._direction = direction; + e.preventDefault(); + } + else + { + if (this._direction === "vertical") + { + this.action.startAction(this._direction, yDiff, position); + } + else if (this._direction === "horizontal") + { + this.action.startAction(this._direction, -xDiff, position); + } + e.preventDefault(); + } + + }); + element.addEventListener("touchend", e => + { + this._xDown = null; + this._yDown = null; + this._direction = null; + if (this.onTouchEnd) + { + this.onTouchEnd(e); + } + }); + } +} +class SwipeAction +{ + constructor(element) + { + this.lowSpeedForward = undefined; + this.lowSpeedBackward = undefined; + this.mediumSpeedForward = undefined; + this.mediumSpeedBackward = undefined; + this.highSpeedForward = undefined; + this.highSpeedBackward = undefined; + + this.lowVolumeUp = undefined; + this.lowVolumeDown = undefined; + this.mediumVolumeUp = undefined; + this.mediumVolumeDown = undefined; + this.highVolumeUp = undefined; + this.highVolumeDown = undefined; + + this.speedCancel = undefined; + this.volumeCancel = undefined; + this.minSwipeDistance = 20; + this.onActionStart = undefined; + this.onActionEnd = undefined; + + this._element = element; + this._touchStart = false; + this._startPosition = null; + this._lastAction = null; + element.addEventListener("touchstart", () => + { + this._touchStart = true; + }); + element.addEventListener("touchend", () => + { + this._startPosition = null; + this.onActionEnd && this.onActionEnd(this._lastAction); + this._lastAction = null; + }); + } + + startAction(direction, distance, position) + { + if (this._touchStart) + { + this.onActionStart && this.onActionStart(); + this._startPosition = position; + this._touchStart = false; + } + if (direction === "vertical") + { + if (Math.abs(distance) < this.minSwipeDistance) + { + this.volumeCancel && this.volumeCancel(); + this._lastAction = null; + } + else + { + let volumeFactor = 0; + let upHandler = undefined; + let downHandler = undefined; + if (this._startPosition.x < 1 / 3) + { + volumeFactor = 0.4; + upHandler = this.lowVolumeUp; + downHandler = this.lowVolumeDown; + } + else if (this._startPosition.x >= 1 / 3 && this._startPosition.x <= 2 / 3) + { + volumeFactor = 1; + upHandler = this.mediumVolumeUp; + downHandler = this.mediumVolumeDown; + } + else + { + volumeFactor = 2; + upHandler = this.highVolumeUp; + downHandler = this.highVolumeDown; + } + + if (distance > 0) + { + const volumeChange = Math.round( + volumeFactor * 100 * (distance - this.minSwipeDistance) / (1.5 * position.height) + ); + upHandler && upHandler(volumeChange); + this._lastAction = { + type: "volume", + volume: volumeChange + }; + } + else + { + const volumeChange = Math.round( + volumeFactor * 100 * (distance + this.minSwipeDistance) / (1.5 * position.height) + ); + downHandler && downHandler(volumeChange); + this._lastAction = { + type: "volume", + volume: volumeChange + }; + } + } + } + else if (direction === "horizontal") + { + if (position.y < 1 / 3 && (position.x < 0.1 || position.x > 0.9) || + Math.abs(distance) < this.minSwipeDistance) + { + this.speedCancel && this.speedCancel(); + this._lastAction = null; + } + else + { + let speedFactor = 0; + let forwardHandler = undefined; + let backwardHandler = undefined; + if (this._startPosition.y < 1 / 3) + { + speedFactor = 0.05; + forwardHandler = this.lowSpeedForward; + backwardHandler = this.lowSpeedBackward; + } + else if (this._startPosition.y >= 1 / 3 && this._startPosition.y <= 2 / 3) + { + speedFactor = 0.2; + forwardHandler = this.mediumSpeedForward; + backwardHandler = this.mediumSpeedBackward; + } + else + { + speedFactor = 1; + forwardHandler = this.highSpeedForward; + backwardHandler = this.highSpeedBackward; + } + + if (distance > 0) + { + const seconds = (distance - this.minSwipeDistance) * speedFactor; + forwardHandler && forwardHandler(seconds); + this._lastAction = { + type: "playback", + seconds: seconds + }; + } + else + { + const seconds = (distance + this.minSwipeDistance) * speedFactor; + backwardHandler && backwardHandler(seconds); + this._lastAction = { + type: "playback", + seconds: seconds + }; + } + } + } + } +} diff --git a/touch/touch-player.min.js b/touch/touch-player.min.js new file mode 100644 index 000000000..a1173b97b --- /dev/null +++ b/touch/touch-player.min.js @@ -0,0 +1 @@ +const getPosition=element=>{let x=0,y=0;for(;element&&!isNaN(element.offsetLeft)&&!isNaN(element.offsetTop);)x+=element.offsetLeft-element.scrollLeft,y+=element.offsetTop-element.scrollTop,element=element.offsetParent;return{x:x,y:y}};class Swiper{constructor(element){this.action=new SwipeAction(element),this.onTouchStart=void 0,this.onTouchEnd=void 0,this._direction=null,element.addEventListener("touchstart",e=>{this._xDown=e.touches[0].clientX,this._yDown=e.touches[0].clientY,this.onTouchStart&&this.onTouchStart(e)}),element.addEventListener("touchmove",e=>{if(!this._xDown||!this._yDown)return;const xUp=e.touches[0].clientX,yUp=e.touches[0].clientY,elementPosition=getPosition(element),position={x:(e.touches[0].pageX-elementPosition.x)/element.clientWidth,y:(e.touches[0].pageY-elementPosition.y)/element.clientHeight,width:element.clientWidth,height:element.clientHeight},xDiff=this._xDown-xUp,yDiff=this._yDown-yUp;if(this._direction)"vertical"===this._direction?this.action.startAction(this._direction,yDiff,position):"horizontal"===this._direction&&this.action.startAction(this._direction,-xDiff,position),e.preventDefault();else{let direction="";direction=Math.abs(xDiff)>Math.abs(yDiff)?"horizontal":"vertical",this._direction=direction,e.preventDefault()}}),element.addEventListener("touchend",e=>{this._xDown=null,this._yDown=null,this._direction=null,this.onTouchEnd&&this.onTouchEnd(e)})}}class SwipeAction{constructor(element){this.lowSpeedForward=void 0,this.lowSpeedBackward=void 0,this.mediumSpeedForward=void 0,this.mediumSpeedBackward=void 0,this.highSpeedForward=void 0,this.highSpeedBackward=void 0,this.lowVolumeUp=void 0,this.lowVolumeDown=void 0,this.mediumVolumeUp=void 0,this.mediumVolumeDown=void 0,this.highVolumeUp=void 0,this.highVolumeDown=void 0,this.speedCancel=void 0,this.volumeCancel=void 0,this.minSwipeDistance=20,this.onActionStart=void 0,this.onActionEnd=void 0,this._element=element,this._touchStart=!1,this._startPosition=null,this._lastAction=null,element.addEventListener("touchstart",()=>{this._touchStart=!0}),element.addEventListener("touchend",()=>{this._startPosition=null,this.onActionEnd&&this.onActionEnd(this._lastAction),this._lastAction=null})}startAction(direction,distance,position){if(this._touchStart&&(this.onActionStart&&this.onActionStart(),this._startPosition=position,this._touchStart=!1),"vertical"===direction)if(Math.abs(distance)=1/3&&this._startPosition.x<=2/3?(volumeFactor=1,upHandler=this.mediumVolumeUp,downHandler=this.mediumVolumeDown):(volumeFactor=2,upHandler=this.highVolumeUp,downHandler=this.highVolumeDown),distance>0){const volumeChange=Math.round(100*volumeFactor*(distance-this.minSwipeDistance)/(1.5*position.height));upHandler&&upHandler(volumeChange),this._lastAction={type:"volume",volume:volumeChange}}else{const volumeChange=Math.round(100*volumeFactor*(distance+this.minSwipeDistance)/(1.5*position.height));downHandler&&downHandler(volumeChange),this._lastAction={type:"volume",volume:volumeChange}}}else if("horizontal"===direction)if(position.y<1/3&&(position.x<.1||position.x>.9)||Math.abs(distance)=1/3&&this._startPosition.y<=2/3?(speedFactor=.2,forwardHandler=this.mediumSpeedForward,backwardHandler=this.mediumSpeedBackward):(speedFactor=1,forwardHandler=this.highSpeedForward,backwardHandler=this.highSpeedBackward),distance>0){const seconds=(distance-this.minSwipeDistance)*speedFactor;forwardHandler&&forwardHandler(seconds),this._lastAction={type:"playback",seconds:seconds}}else{const seconds=(distance+this.minSwipeDistance)*speedFactor;backwardHandler&&backwardHandler(seconds),this._lastAction={type:"playback",seconds:seconds}}}}} \ No newline at end of file diff --git a/utils/common.min.js b/utils/common.min.js index 0e658cb59..0b91171a8 100644 --- a/utils/common.min.js +++ b/utils/common.min.js @@ -1 +1 @@ -const waitForQuery=settings=>{const MaxRetry=settings.maxQueryRetry;let retry=0;const tryQuery=(query,condition,action,failed)=>{if(retry>=MaxRetry)failed&&failed();else{const result=query();condition(result)?action(result):(retry++,setTimeout(()=>tryQuery(query,condition,action,failed),settings.queryInterval))}};return tryQuery}; \ No newline at end of file +const waitForQuery=settings=>{const MaxRetry=settings.maxQueryRetry;let retry=0;const tryQuery=(query,condition,action,failed)=>{if(retry>=MaxRetry){if(failed){failed()}}else{const result=query();if(condition(result)){action(result)}else{retry++;setTimeout(()=>tryQuery(query,condition,action,failed),settings.queryInterval)}}};return tryQuery}; \ No newline at end of file