Replace ddys Origin Player

替换ddys播放器,移除Adblock屏蔽,修复滚轮和全屏快捷键失效bug,优化选集和线路功能,自动记忆选集

目前為 2023-07-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Replace ddys Origin Player
  3. // @namespace https://github.com/s0urcelab/replace-ddys-origin-player
  4. // @version 1.3
  5. // @description 替换ddys播放器,移除Adblock屏蔽,修复滚轮和全屏快捷键失效bug,优化选集和线路功能,自动记忆选集
  6. // @author s0urce
  7. // @match https://ddys.art/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=ddys.art
  9. // @grant GM_addStyle
  10. // @grant GM_xmlhttpRequest
  11. // @require https://fastly.jsdelivr.net/npm/xgplayer@2.31.2/browser/index.min.js
  12. // @run-at document-end
  13. // ==/UserScript==
  14.  
  15. const QS = (q) => document.querySelector(q)
  16. const QSA = (q) => document.querySelectorAll(q)
  17.  
  18. const domain = window.location.hostname
  19. const src4domain = `v.ddys.pro`
  20.  
  21. const globalStyle = `
  22. .wp-playlist-tracks {
  23. display: none!important;
  24. }
  25. .wp-video-playlist {
  26. display: flex;
  27. padding: 0!important;
  28. border: none!important;
  29. background: none!important;
  30. }
  31. .entry > p {
  32. display: none;
  33. }
  34. .player-sider {
  35. width: 220px;
  36. display: flex;
  37. flex-direction: column;
  38. background-color: #2e2e2e;
  39. border-radius: 8px;
  40. margin-left: 10px;
  41. padding: 4px;
  42. }
  43. .tab-item {
  44. cursor: pointer;
  45. margin-bottom: 6px;
  46. padding: 8px;
  47. color: white;
  48. background-color: #5a5a5a;
  49. border-radius: 5px;
  50. }
  51. .tab-item.playing {
  52. font-weight: bold;
  53. color: #3a8fb7;
  54. background-color: #232323;
  55. }
  56. .tab-item:not(.playing):hover {
  57. background-color: #232323;
  58. }
  59. .tab-item > .indicator {
  60. height: 14px;
  61. width: 14px;
  62. font-size: 14px;
  63. margin-right: 5px;
  64. }
  65. .switch-root {
  66. cursor: pointer;
  67. user-select: none;
  68. position: relative;
  69. background: #e0e0e0;
  70. border-radius: 26px;
  71. width: 174px;
  72. padding: 2px;
  73. }
  74. .sw-group {
  75. position: absolute;
  76. top: 0;
  77. left: 0;
  78. padding: 2px;
  79. width: 170px;
  80. display: flex;
  81. justify-content: space-between;
  82. }
  83. .sw-item {
  84. line-height: 30px;
  85. padding: 0 10px;
  86. color: #666;
  87. }
  88. .sw-item.active {
  89. font-weight: bold;
  90. color: #333;
  91. }
  92. .switch-root > .indicator {
  93. transition: all 0.2s ease;
  94. margin-left: 0;
  95. width: 86px;
  96. height: 30px;
  97. background: #fff;
  98. border-radius: 26px;
  99. box-shadow: 0px 0px 6px -2px #111;
  100. }
  101. .switch-root > .indicator.overseas {
  102. margin-left: 84px;
  103. }
  104. .ep-tip {
  105. margin: 10px 0;
  106. color: white;
  107. }
  108. `
  109. function parseResUrl(region, d) {
  110. // type 4
  111. if (d.srctype === '4') return { ...d, url: `https://${src4domain}${d.src3}` }
  112. // 海外线路
  113. if (region == 'overseas') return { ...d, url: `https://w.ddys.art${d.src0}?ddrkey=${d.src2}` }
  114.  
  115. return new Promise((resolve, reject) => {
  116. GM_xmlhttpRequest({
  117. method: 'GET',
  118. responseType: 'json',
  119. headers: {
  120. 'referer': `https://${domain}/`
  121. },
  122. url: `https://${domain}/getvddr2/video?id=${d.src1}&type=json`,
  123. onload: res => {
  124. resolve({ ...d, url: res.response.url })
  125. },
  126. onerror: function (error) {
  127. reject(error)
  128. },
  129. })
  130. })
  131. }
  132.  
  133. class Tabs {
  134. constructor(init) {
  135. this.root = init.root
  136. this.data = init.data
  137. this.onSelect = init.onSelect
  138. this.selectedKey = init.data[0].key
  139. }
  140.  
  141. render(key = this.selectedKey) {
  142. // update selectedKey
  143. this.selectedKey = key
  144. // render dom
  145. this.root.innerHTML = this.data.reduce((acc, curr) => {
  146. const isTarget = key === curr.key
  147. return `${acc}
  148. <div class="tab-item ${isTarget ? 'playing' : ''}" data-tab-key="${curr.key}">
  149. ${isTarget ? '<img class="indicator" src="//s1.hdslb.com/bfs/static/jinkela/video/asserts/playing.gif"></img>' : ''}
  150. ${curr.label}
  151. </div>
  152. `
  153. }, '')
  154. // bind click
  155. const self = this
  156. for (const tabElment of this.root.children) {
  157. tabElment.onclick = function() {
  158. const tabKey = tabElment.dataset.tabKey
  159. const record = self.data.find(v => v.key === tabKey)
  160. self.render(tabKey)
  161. self.onSelect(tabKey, record)
  162. }
  163. }
  164. }
  165. }
  166. class Switch {
  167. constructor(init) {
  168. this.root = init.root
  169. this.data = init.data
  170. this.onSelect = init.onSwitch
  171. this.selectedKey = init.data[0].key
  172. }
  173.  
  174. render(key = this.selectedKey) {
  175. // update selectedKey
  176. this.selectedKey = key
  177. // render dom
  178. const group = this.data.reduce((acc, curr) => {
  179. const isTarget = key === curr.key
  180. return `${acc}
  181. <div class="sw-item ${isTarget ? 'active' : ''}" data-sw-key="${curr.key}">
  182. ${curr.label}
  183. </div>
  184. `
  185. }, '')
  186. this.root.innerHTML = `<div class="indicator ${key}"></div><div class="sw-group">${group}</div>`
  187. // bind click
  188. const self = this
  189. for (const swElment of this.root.querySelector('.sw-group').children) {
  190. swElment.onclick = function() {
  191. const swKey = swElment.dataset.swKey
  192. const record = self.data.find(v => v.key === swKey)
  193. self.render(swKey)
  194. self.onSelect(swKey, record)
  195. }
  196. }
  197. }
  198. }
  199.  
  200. ; (async function () {
  201. 'use strict';
  202.  
  203. const originContainer = QS('.wp-video-playlist')
  204. // cannot found Player, quit
  205. if (!originContainer) return;
  206.  
  207. // inject global style
  208. GM_addStyle(globalStyle)
  209. // hide origin container
  210. for (const item of originContainer.children) {
  211. item.style.display = 'none'
  212. }
  213.  
  214. // append container for xgplayer
  215. originContainer.innerHTML += `
  216. <div id="xgplayer"></div>
  217. <div class="player-sider">
  218. <div class="switch-root"></div>
  219. <p class="ep-tip">选集:</p>
  220. <div class="tabs-root"></div>
  221. </div>
  222. `
  223. // get video resource from page data
  224. const res = JSON.parse(QS('.wp-playlist-script').textContent)
  225. const resPromise = res.tracks
  226. .map((track, idx) => ({ ...track, key: `${idx + 1}`, label: track.caption }))
  227. .map(d => parseResUrl(window.localStorage['region'], d))
  228. const resGroups = await Promise.all(resPromise)
  229.  
  230. // init xgplayer
  231. const initVolume = window.localStorage['volume'] ? parseFloat(window.localStorage['volume']) : 1
  232. const isWatched = window.localStorage[location.pathname]
  233. const initEp = isWatched ? JSON.parse(isWatched).ep : '1'
  234. const initPlayUrl = resGroups.find(v => v.key === initEp).url
  235.  
  236. console.warn(`当前播放资源url${initPlayUrl}`)
  237. const player = new window.Player({
  238. id: 'xgplayer',
  239. url: initPlayUrl,
  240. volume: initVolume,
  241. fluid: true,
  242. videoInit: true,
  243. lastPlayTimeHideDelay: 3,
  244. ...isWatched && {lastPlayTime: JSON.parse(isWatched).seek},
  245. })
  246.  
  247. // init switch
  248. const switchs = new Switch({
  249. root: QS('.switch-root'),
  250. data: [{ key: 'domestic', label: '国内线路' }, { key: 'overseas', label: '海外线路' }],
  251. onSwitch: (key, record) => {
  252. console.warn(`切换线路:${record.label},即将刷新页面`)
  253. window.localStorage['region'] = key
  254. window.location.reload()
  255. }
  256. })
  257. switchs.render(window.localStorage['region'])
  258. // init tabs
  259. const tabs = new Tabs({
  260. root: QS('.tabs-root'),
  261. data: resGroups,
  262. onSelect: (key, record) => {
  263. console.warn(`切换选集:【${key}】${record.label}`)
  264. player.src = record.url
  265. console.warn(`当前播放资源url${record.url}`)
  266. player.play()
  267. }
  268. })
  269. // render tabs
  270. tabs.render(initEp)
  271.  
  272. // update video progress
  273. player.on('timeupdate', function({ currentTime }) {
  274. window.localStorage[location.pathname] = JSON.stringify({
  275. seek: currentTime,
  276. ep: tabs.selectedKey,
  277. })
  278. })
  279. // update volume
  280. player.on('volumechange', function({ volume }) {
  281. window.localStorage['volume'] = volume
  282. })
  283.  
  284. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址