dps

自用阿里云依赖库

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/477468/1265011/dps.js

  1. window.dpPlugins = window.dpPlugins || function (t) {
  2. var obj = {};
  3.  
  4. obj.init = function (player, option) {
  5. obj = Object.assign(option || {}, obj);
  6.  
  7. obj.ready(player).then(() => {
  8. t.forEach((k) => {
  9. new k(player, obj);
  10. });
  11. });
  12. };
  13.  
  14. obj.ready = function (player) {
  15. return new Promise(function (resolve, reject) {
  16. if (player.isReady) {
  17. resolve();
  18. }
  19. else if (player.video.duration > 0 || player.video.readyState > 2) {
  20. player.isReady = true;
  21. resolve();
  22. }
  23. else {
  24. player.video.ondurationchange = function () {
  25. player.video.ondurationchange = null;
  26. player.isReady = true;
  27. resolve();
  28. }
  29. }
  30. });
  31. };
  32.  
  33. obj.query = function (selector, parent = document) {
  34. return parent.querySelector(selector);
  35. };
  36.  
  37. obj.queryAll = function (selector, parent = document) {
  38. return parent.querySelectorAll(selector);
  39. };
  40.  
  41. obj.append = function (parent, child) {
  42. if (child instanceof Element) {
  43. Node.prototype.appendChild.call(parent, child);
  44. }
  45. else {
  46. parent.insertAdjacentHTML("beforeend", String(child));
  47. }
  48. return parent.lastElementChild || parent.lastChild;
  49. };
  50.  
  51. obj.prepend = function (parent, child) {
  52. if (child instanceof Element) {
  53. Node.prototype.insertBefore.call(parent, child, parent.firstElementChild || parent.firstChild);
  54. }
  55. else {
  56. parent.insertAdjacentHTML("afterbegin", String(child));
  57. }
  58. return parent.firstElementChild || parent.firstChild;
  59. };
  60.  
  61. obj.insertAfter = function (targetElement, child) {
  62. var parent = targetElement.parentNode;
  63. if (parent.lastChild == targetElement) {
  64. return obj.append(parent, child);
  65. }
  66. else {
  67. child = obj.append(parent, child);
  68. return Node.prototype.insertBefore.call(parent, child, targetElement.nextSibling);
  69. }
  70. };
  71.  
  72. obj.remove = function (child) {
  73. return Node.prototype.removeChild.call(child?.parentNode || document, child);
  74. };
  75.  
  76. obj.setStyle = function (element, key, value) {
  77. if (typeof key === 'object') {
  78. for (const k in key) {
  79. element.style[k] = key[k];
  80. }
  81. return element;
  82. }
  83. element.style[key] = value;
  84. return element;
  85. };
  86.  
  87. return obj;
  88. }([
  89. class HlsEvents {
  90. constructor(player) {
  91. this.player = player;
  92. this.hls = this.player.plugins.hls;
  93.  
  94. if (!this.player.events.type('video_end')) {
  95. this.player.events.playerEvents.push('video_end');
  96. }
  97. this.player.on('video_end', () => {
  98. this.switchVideo();
  99. });
  100.  
  101. this.player.on('quality_end', () => {
  102. if (this.hls) {
  103. this.hls.destroy();
  104. this.hls = this.player.plugins.hls;
  105. this.onEvents();
  106. localStorage.setItem("dplayer-defaultQuality", this.player.quality.name);
  107. }
  108. });
  109.  
  110. this.player.on('destroy', () => {
  111. if (this.hls) {
  112. this.hls.destroy();
  113. }
  114. });
  115.  
  116. this.onEvents();
  117. }
  118.  
  119. switchVideo() {
  120. const { paused, currentTime, playbackRate, muted } = this.player.video;
  121. const videoHTML = '<video class="dplayer-video" webkit-playsinline playsinline crossorigin="anonymous" preload="auto" src="' + this.player.quality.url + '"><track kind="metadata" default src=""></track></video>';
  122. const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
  123. this.player.template.videoWrap.insertBefore(videoEle, this.player.template.videoWrap.getElementsByTagName('div')[0]);
  124. this.player.prevVideo = this.player.video;
  125. this.player.video = videoEle;
  126. this.player.initVideo(this.player.video, this.player.quality.type || this.player.options.video.type);
  127.  
  128. this.player.video.onloadeddata = () => {
  129. this.player.video.onloadeddata = null;
  130. this.player.video.currentTime = this.player.prevVideo.currentTime;
  131. };
  132.  
  133. const now = Date.now();
  134. this.player.video.oncanplaythrough = () => {
  135. this.player.video.oncanplaythrough = null;
  136. this.player.video.currentTime = Math.max(this.player.prevVideo.currentTime, currentTime + (Date.now() - now) / 1000);
  137. this.player.video.muted = muted;
  138. this.player.speed(playbackRate);
  139.  
  140. if (!paused) {
  141. this.player.play();
  142. }
  143.  
  144. this.player.video.onplaying = () => {
  145. this.player.video.onplaying = null;
  146. this.player.video.currentTime = Math.max(this.player.prevVideo.currentTime, currentTime + (Date.now() - now) / 1000);
  147. this.player.controller.hide();
  148.  
  149. this.player.prevVideo.pause();
  150. this.player.template.videoWrap.removeChild(this.player.prevVideo);
  151. this.player.template.video = this.player.video;
  152. this.player.video.classList.add('dplayer-video-current');
  153. this.player.prevVideo = null;
  154.  
  155. while (this.player.template.videoWrap.querySelectorAll('video').length > 1) {
  156. this.player.template.videoWrap.removeChild(this.player.template.videoWrap.getElementsByTagName('video')[1]);
  157. }
  158.  
  159. this.player.events.trigger('quality_end');
  160. };
  161. };
  162. }
  163.  
  164. onEvents() {
  165. const Hls = window.Hls;
  166. this.hls.once(Hls.Events.ERROR, (event, data) => {
  167. if (this.isUrlExpires(data?.frag?.relurl || data?.response?.url || this.hls.url)) {
  168. this.player.events.trigger('video_start');
  169. }
  170. else {
  171. this.onEvents();
  172. }
  173. });
  174. };
  175.  
  176. isUrlExpires(e) {
  177. var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 6e3
  178. , n = e.match(/&x-oss-expires=(\d+)&/);
  179. return !n || n && n[1] && +"".concat(n[1], "000") - t < Date.now();
  180. };
  181. },
  182. class ImageEnhancer {
  183. constructor(player, obj) {
  184. this.player = player;
  185.  
  186. Object.assign(this.player.user.storageName, { imageenhancer: "dplayer-imageenhancer" });
  187. Object.assign(this.player.user.default, { imageenhancer: 0 });
  188. this.player.user.init();
  189. this.imageenhancer = this.player.user.get("imageenhancer");
  190. if (this.imageenhancer) {
  191. this.player.video.style.filter = 'contrast(1.01) brightness(1.05) saturate(1.1)';
  192. }
  193.  
  194. this.player.template.imageEnhancer = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-imageenhancer"><span class="dplayer-label">画质增强</span><div class="dplayer-toggle"><input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle"><label for="dplayer-toggle"></label></div></div>');
  195. this.player.template.imageEnhancerToggle = obj.query('input', this.player.template.imageEnhancer);
  196. this.player.template.imageEnhancerToggle.checked = this.imageenhancer;
  197.  
  198. this.player.template.imageEnhancer.addEventListener('click', () => {
  199. this.imageenhancer = this.player.template.imageEnhancerToggle.checked = !this.player.template.imageEnhancerToggle.checked;
  200. this.player.user.set("imageenhancer", Number(this.imageenhancer));
  201. this.player.video.style.filter = this.imageenhancer ? 'contrast(1.01) brightness(1.05) saturate(1.1)' : '';
  202. this.player.notice(`画质增强: ${this.imageenhancer ? '开启' : '关闭'}`);
  203. });
  204.  
  205. this.player.on("playing", () => {
  206. if (this.imageenhancer) {
  207. this.player.video.style.filter = 'contrast(1.01) brightness(1.05) saturate(1.1)';
  208. }
  209. });
  210. }
  211. },
  212. class SoundEnhancer {
  213. constructor(player, obj) {
  214. this.player = player;
  215. this.Joysound = window.Joysound || unsafeWindow.Joysound;
  216. this.joySound = null;
  217. this.offset = null;
  218.  
  219. Object.assign(this.player.user.storageName, { soundenhancer: "dplayer-soundenhancer", volumeenhancer: "dplayer-volumeenhancer" });
  220. Object.assign(this.player.user.default, { soundenhancer: 0, volumeenhancer: 0 });
  221. this.player.user.init();
  222.  
  223. /*** SoundEnhancer ***/
  224. this.player.template.soundEnhancer = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-soundenhancer"><span class="dplayer-label">音质增强</span><div class="dplayer-toggle"><input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle"><label for="dplayer-toggle"></label></div></div>');
  225. this.player.template.soundEnhancerToggle = obj.query('input', this.player.template.soundEnhancer);
  226. this.player.template.soundEnhancerToggle.checked = !!this.player.user.get("soundenhancer");
  227.  
  228. this.player.template.soundEnhancer.addEventListener('click', () => {
  229. let checked = this.player.template.soundEnhancerToggle.checked = !this.player.template.soundEnhancerToggle.checked;
  230. this.player.user.set("soundenhancer", Number(checked));
  231. this.switchJoysound(checked);
  232. });
  233.  
  234. /*** VolumeEnhancer ***/
  235. this.player.template.gainBox = obj.prepend(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-danmaku dplayer-setting-gain" style="display: block;"><span class="dplayer-label">音量增强</span><div class="dplayer-danmaku-bar-wrap dplayer-gain-bar-wrap"><div class="dplayer-danmaku-bar dplayer-gain-bar"><div class="dplayer-danmaku-bar-inner dplayer-gain-bar-inner" style="width: 0%;"><span class="dplayer-thumb"></span></div></div></div></div>');
  236. this.player.template.gainBarWrap = this.player.template.gainBox.querySelector('.dplayer-gain-bar-wrap');
  237. this.player.bar.elements.gain = this.player.template.gainBox.querySelector('.dplayer-gain-bar-inner');
  238.  
  239. const gainMove = (event) => {
  240. const e = event || window.event;
  241. let percentage = ((e.clientX || e.changedTouches[0].clientX) - this.getElementViewLeft(this.player.template.gainBarWrap)) / 130;
  242. this.switchGainValue(percentage);
  243. };
  244. const gainUp = () => {
  245. document.removeEventListener("touchend", gainUp);
  246. document.removeEventListener("touchmove", gainMove);
  247. document.removeEventListener("mouseup", gainUp);
  248. document.removeEventListener("mousemove", gainMove);
  249. this.player.template.gainBox.classList.remove('dplayer-setting-danmaku-active');
  250. };
  251.  
  252. this.player.template.gainBarWrap.addEventListener('click', (event) => {
  253. const e = event || window.event;
  254. let percentage = ((e.clientX || e.changedTouches[0].clientX) - this.getElementViewLeft(this.player.template.gainBarWrap)) / 130;
  255. this.switchGainValue(percentage);
  256. });
  257. this.player.template.gainBarWrap.addEventListener("touchstart", () => {
  258. document.addEventListener("touchmove", gainMove);
  259. document.addEventListener("touchend", gainUp);
  260. this.player.template.gainBox.classList.add('dplayer-setting-danmaku-active');
  261. });
  262. this.player.template.gainBarWrap.addEventListener("mousedown", () => {
  263. document.addEventListener("mousemove", gainMove);
  264. document.addEventListener("mouseup", gainUp);
  265. this.player.template.gainBox.classList.add('dplayer-setting-danmaku-active');
  266. });
  267.  
  268. this.player.on("playing", () => {
  269. if (!this.player.video.joySound) {
  270. this.init();
  271. }
  272. });
  273. }
  274.  
  275. init() {
  276. if (this.Joysound && this.Joysound.isSupport()) {
  277. this.joySound = new this.Joysound();
  278. this.joySound.init(this.player.video);
  279. this.player.video.joySound = true;
  280. let isJoysound = this.player.user.get("soundenhancer");
  281. if (isJoysound) {
  282. this.switchJoysound(isJoysound);
  283. }
  284. let percentage = this.player.user.get("volumeenhancer");
  285. if (percentage) {
  286. this.switchGainValue(percentage);
  287. }
  288. }
  289. }
  290.  
  291. switchJoysound(o) {
  292. if (this.joySound) {
  293. this.joySound.setEnabled(o);
  294. this.player.notice(`音质增强: ${o ? '开启' : '关闭'}`);
  295. } else {
  296. this.player.notice('Joysound 未完成初始化');
  297. }
  298. }
  299.  
  300. switchGainValue(percentage) {
  301. if (this.joySound) {
  302. percentage = Math.min(Math.max(percentage, 0), 1);
  303. this.player.bar.set("gain", percentage, "width");
  304. this.player.user.set("volumeenhancer", percentage);
  305. this.joySound.setVolume(percentage);
  306. this.player.notice(`音量增强: ${100 + Math.floor(percentage * 100)}%`);
  307. } else {
  308. this.player.notice('Joysound 未完成初始化');
  309. }
  310. }
  311.  
  312. getElementViewLeft(element) {
  313. const scrollTop = window.scrollY || window.pageYOffset || document.body.scrollTop + ((document.documentElement && document.documentElement.scrollTop) || 0);
  314. if (element.getBoundingClientRect) {
  315. if (typeof this.offset !== 'number') {
  316. let temp = document.createElement('div');
  317. temp.style.cssText = 'position:absolute;top:0;left:0;';
  318. document.body.appendChild(temp);
  319. this.offset = -temp.getBoundingClientRect().top - scrollTop;
  320. document.body.removeChild(temp);
  321. temp = null;
  322. }
  323. const rect = element.getBoundingClientRect();
  324. return rect.left + this.offset;
  325. } else {
  326. let actualLeft = element.offsetLeft;
  327. let current = element.offsetParent;
  328. const elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;
  329. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
  330. while (current !== null) {
  331. actualLeft += current.offsetLeft;
  332. current = current.offsetParent;
  333. }
  334. } else {
  335. while (current !== null && current !== element) {
  336. actualLeft += current.offsetLeft;
  337. current = current.offsetParent;
  338. }
  339. }
  340. return actualLeft - elementScrollLeft;
  341. }
  342. }
  343. },
  344. class AspectRatio {
  345. constructor(player) {
  346. this.player = player;
  347. this.value = null;
  348.  
  349. this.player.template.controller.querySelector('.dplayer-icons-right').insertAdjacentHTML("afterbegin", '<div class="dplayer-quality dplayer-aspectRatio"><button class="dplayer-icon dplayer-quality-icon">画面比例</button><div class="dplayer-quality-mask"><div class="dplayer-quality-list dplayer-aspectRatio-list"><div class="dplayer-quality-item" data-value="none">原始比例</div><div class="dplayer-quality-item" data-value="cover">自动裁剪</div><div class="dplayer-quality-item" data-value="fill">拉伸填充</div><div class="dplayer-quality-item" data-value="">系统默认</div></div></div></div>');
  350. this.player.template.aspectRatioButton = this.player.template.controller.querySelector('.dplayer-aspectRatio button');
  351. this.player.template.aspectRatioList = this.player.template.controller.querySelector('.dplayer-aspectRatio-list');
  352.  
  353. this.player.template.aspectRatioList.addEventListener('click', (e) => {
  354. if (e.target.classList.contains('dplayer-quality-item')) {
  355. this.value = e.target.dataset.value;
  356. this.player.video.style['object-fit'] = e.target.dataset.value;
  357. this.player.template.aspectRatioButton.innerText = e.target.innerText;
  358. }
  359. });
  360.  
  361. this.player.on("playing", () => {
  362. if (this.value) {
  363. this.player.video.style['object-fit'] = this.value;
  364. }
  365. });
  366. }
  367. },
  368. class SelectEpisode {
  369. constructor(player, obj) {
  370. this.player = player;
  371.  
  372. if (!this.player.events.type('episode_end')) {
  373. this.player.events.playerEvents.push('episode_end');
  374. }
  375. this.player.on('episode_end', () => {
  376. this.switchVideo();
  377. });
  378.  
  379. if (Array.isArray(this.player.options.fileList) && this.player.options.fileList.length > 1 && this.player.options.file) {
  380. this.player.fileIndex = (this.player.options.fileList || []).findIndex((item, index) => {
  381. return item.file_id === this.player.options.file.file_id;
  382. });
  383.  
  384. obj.prepend(this.player.template.controller.querySelector('.dplayer-icons-right'), '<style>.episode .content{max-width: 360px;max-height: 330px;width: auto;height: auto;box-sizing: border-box;overflow: hidden auto;position: absolute;left: 0px;transition: all 0.38s ease-in-out 0s;bottom: 52px;transform: scale(0);z-index: 2;}.episode .content .list{background-color: rgba(0,0,0,.3);height: 100%;}.episode .content .video-item{color: #fff;cursor: pointer;font-size: 14px;line-height: 35px;overflow: hidden;padding: 0 10px;text-overflow: ellipsis;text-align: center;white-space: nowrap;}.episode .content .active{background-color: rgba(0,0,0,.3);color: #0df;}</style><div class="dplayer-quality episode"><button class="dplayer-icon prev-icon" title="上一集"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M757.527273 190.138182L382.510545 490.123636a28.020364 28.020364 0 0 0 0 43.752728l375.016728 299.985454a28.020364 28.020364 0 0 0 45.474909-21.876363V212.014545a28.020364 28.020364 0 0 0-45.474909-21.876363zM249.949091 221.509818a28.020364 28.020364 0 0 0-27.973818 27.973818v525.032728a28.020364 28.020364 0 1 0 55.994182 0V249.483636a28.020364 28.020364 0 0 0-28.020364-27.973818zM747.054545 270.242909v483.514182L444.834909 512l302.173091-241.757091z"></path></svg></button><button class="dplayer-icon dplayer-quality-icon episode-icon">选集</button><button class="dplayer-icon next-icon" title="下一集"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M248.506182 190.138182l374.970182 299.985454a28.020364 28.020364 0 0 1 0 43.752728L248.552727 833.861818a28.020364 28.020364 0 0 1-45.521454-21.876363V212.014545c0-23.505455 27.182545-36.538182 45.521454-21.876363z m507.485091 31.371636c15.453091 0 28.020364 12.567273 28.020363 27.973818v525.032728a28.020364 28.020364 0 1 1-55.994181 0V249.483636c0-15.453091 12.520727-27.973818 27.973818-27.973818zM258.978909 270.242909v483.514182L561.198545 512 258.978909 270.242909z"></path></svg></button><div class="content"><div class="list"></div></div></div>')
  385. this.player.template.episodeButton = this.player.template.controller.querySelector('.episode .episode-icon');
  386. this.player.template.episodePrevButton = this.player.template.controller.querySelector('.episode .prev-icon');
  387. this.player.template.episodeNextButton = this.player.template.controller.querySelector('.episode .next-icon');
  388. this.player.template.episodeContent = this.player.template.controller.querySelector('.episode .content');
  389. this.player.template.episodeList = this.player.template.controller.querySelector('.episode .list');
  390. this.player.options.fileList.forEach((item, index) => {
  391. obj.append(this.player.template.episodeList, '<div class="video-item" data-index="' + index + '" title="' + item.name + '">' + item.name + '</div>');
  392. });
  393. this.player.template.episodeVideoItems = this.player.template.controller.querySelectorAll('.episode .video-item');
  394. this.player.template.episodeVideoItems[this.player.fileIndex].classList.add('active');
  395.  
  396. this.player.template.mask.addEventListener('click', () => {
  397. this.hide();
  398. });
  399.  
  400. this.player.template.episodeButton.addEventListener('click', (e) => {
  401. if (this.player.template.episodeContent.style.transform === 'scale(1)') {
  402. this.hide();
  403. }
  404. else {
  405. this.show();
  406. }
  407. });
  408.  
  409. this.player.template.episodeList.addEventListener('click', (e) => {
  410. if (e.target.classList.contains('video-item') && !e.target.classList.contains('active')) {
  411. this.player.template.episodeVideoItems[this.player.fileIndex].classList.remove('active');
  412. e.target.classList.add('active');
  413. this.player.fileIndex = e.target.dataset.index * 1;
  414. this.player.options.file = this.player.options.fileList[this.player.fileIndex];
  415.  
  416. this.hide();
  417. this.player.events.trigger('episode_start');
  418. this.player.notice('准备播放:' + this.player.options.file.name, 5000);
  419. }
  420. });
  421.  
  422. this.player.template.episodePrevButton.addEventListener('click', (e) => {
  423. const index = this.player.fileIndex - 1;
  424. if (index >= 0) {
  425. this.player.template.episodeVideoItems[this.player.fileIndex].classList.remove('active');
  426. this.player.template.episodeVideoItems[index].classList.add('active');
  427. this.player.fileIndex = index;
  428. this.player.options.file = this.player.options.fileList[this.player.fileIndex];
  429.  
  430. this.hide();
  431. this.player.events.trigger('episode_start');
  432. this.player.notice('准备播放:' + this.player.options.file.name, 5000);
  433. }
  434. else {
  435. this.player.notice('没有上一集了');
  436. }
  437. });
  438.  
  439. this.player.template.episodeNextButton.addEventListener('click', (e) => {
  440. const index = this.player.fileIndex + 1;
  441. if (index <= this.player.options.fileList.length - 1) {
  442. this.player.template.episodeVideoItems[this.player.fileIndex].classList.remove('active');
  443. this.player.template.episodeVideoItems[index].classList.add('active');
  444. this.player.fileIndex = index;
  445. this.player.options.file = this.player.options.fileList[this.player.fileIndex];
  446.  
  447. this.hide();
  448. this.player.events.trigger('episode_start');
  449. this.player.notice('准备播放:' + this.player.options.file.name, 5000);
  450. }
  451. else {
  452. this.player.notice('没有下一集了');
  453. }
  454. });
  455. }
  456. }
  457.  
  458. switchVideo() {
  459. const videoHTML = '<video class="dplayer-video" webkit-playsinline playsinline crossorigin="anonymous" preload="auto" src="' + this.player.quality.url + '"><track kind="metadata" default src=""></track></video>';
  460. const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
  461. this.player.template.videoWrap.insertBefore(videoEle, this.player.template.videoWrap.getElementsByTagName('div')[0]);
  462. this.player.prevVideo = this.player.video;
  463. this.player.video = videoEle;
  464. this.player.initVideo(this.player.video, this.player.quality.type || this.player.options.video.type);
  465. this.player.video.currentTime = 0;
  466. this.player.controller.hide();
  467.  
  468. this.player.video.oncanplaythrough = () => {
  469. this.player.video.oncanplaythrough = null;
  470. this.player.video.currentTime = 0;
  471. this.player.events.trigger('quality_end');
  472.  
  473. this.player.play();
  474. this.player.controller.hide();
  475.  
  476. if (this.player.prevVideo) {
  477. this.player.prevVideo.pause && this.player.prevVideo.pause();
  478. this.player.template.videoWrap.removeChild(this.player.prevVideo);
  479. this.player.template.video = this.player.video;
  480. this.player.video.classList.add('dplayer-video-current');
  481. this.player.prevVideo = null;
  482. }
  483.  
  484. while (this.player.template.videoWrap.querySelectorAll('video').length > 1) {
  485. this.player.template.videoWrap.removeChild(this.player.template.videoWrap.getElementsByTagName('video')[1]);
  486. }
  487. };
  488. }
  489.  
  490. show() {
  491. this.player.template.episodeContent.style.transform = 'scale(1)';
  492. this.player.template.mask.classList.add('dplayer-mask-show');
  493. }
  494.  
  495. hide() {
  496. this.player.template.episodeContent.style.transform = 'scale(0)';
  497. this.player.template.mask.classList.remove('dplayer-mask-show');
  498. }
  499. },
  500. class AutoNextEpisode {
  501. constructor(player, obj) {
  502. this.player = player;
  503.  
  504. Object.assign(this.player.user.storageName, { autonextepisode: "dplayer-autonextepisode" });
  505. Object.assign(this.player.user.default, { autonextepisode: 0 });
  506. this.player.user.init();
  507.  
  508. this.autonextepisode = this.player.user.get("autonextepisode");
  509.  
  510. this.player.template.autoNextEpisode = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-autonextepisode"><span class="dplayer-label">自动下一集</span><div class="dplayer-toggle"><input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle"><label for="dplayer-toggle"></label></div></div>');
  511. this.player.template.autoNextEpisodeToggle = obj.query('input', this.player.template.autoNextEpisode);
  512. this.player.template.autoNextEpisodeToggle.checked = this.autonextepisode;
  513.  
  514. this.player.template.autoNextEpisode.addEventListener('click', () => {
  515. this.autonextepisode = this.player.template.autoNextEpisodeToggle.checked = !this.player.template.autoNextEpisodeToggle.checked;
  516. this.player.user.set("autonextepisode", Number(this.autonextepisode));
  517. this.player.notice(`自动播放下集: ${this.autonextepisode ? '开启' : '关闭'}`);
  518. });
  519.  
  520. this.player.on("ended", () => {
  521. if (this.autonextepisode) {
  522. this.player.template.episodeNextButton && this.player.template.episodeNextButton.click();
  523. }
  524. });
  525. }
  526. },
  527. class MemoryPlay {
  528. constructor(player, obj) {
  529. this.player = player;
  530. this.file_id = this.player.options?.file?.file_id;
  531. this.hasMemoryDisplay = false;
  532.  
  533. Object.assign(this.player.user.storageName, { automemoryplay: "dplayer-automemoryplay" });
  534. Object.assign(this.player.user.default, { automemoryplay: 0 });
  535. this.player.user.init();
  536. this.automemoryplay = this.player.user.get("automemoryplay");
  537.  
  538. this.player.template.autoMemoryPlay = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-automemoryplay"><span class="dplayer-label">自动记忆播放</span><div class="dplayer-toggle"><input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle"><label for="dplayer-toggle"></label></div></div>');
  539. this.player.template.autoMemoryPlayToggle = obj.query('input', this.player.template.autoMemoryPlay);
  540. this.player.template.autoMemoryPlayToggle.checked = this.automemoryplay;
  541.  
  542. this.player.template.autoMemoryPlay.addEventListener('click', () => {
  543. this.automemoryplay = this.player.template.autoMemoryPlayToggle.checked = !this.player.template.autoMemoryPlayToggle.checked;
  544. this.player.user.set("automemoryplay", Number(this.automemoryplay));
  545. this.player.notice(`自动记忆播放: ${this.automemoryplay ? '开启' : '关闭'}`);
  546. });
  547.  
  548. this.player.on('quality_end', () => {
  549. if (this.file_id !== this.player.options?.file?.file_id) {
  550. this.file_id = this.player.options?.file?.file_id;
  551. this.hasMemoryDisplay = false;
  552. }
  553. this.run();
  554. });
  555.  
  556. document.onvisibilitychange = () => {
  557. if (document.visibilityState === 'hidden') {
  558. const { video: { currentTime, duration } } = this.player;
  559. this.setTime(this.file_id, currentTime, duration);
  560. }
  561. };
  562.  
  563. window.onbeforeunload = () => {
  564. const { video: { currentTime, duration } } = this.player;
  565. this.setTime(this.file_id, currentTime, duration);
  566. };
  567.  
  568. this.run();
  569. }
  570.  
  571. run() {
  572. if (this.hasMemoryDisplay === false) {
  573. this.hasMemoryDisplay = true;
  574.  
  575. const { video: { currentTime, duration } } = this.player;
  576. const memoryTime = this.getTime(this.file_id);
  577. if (memoryTime && memoryTime > currentTime) {
  578. if (this.automemoryplay) {
  579. this.player.seek(memoryTime);
  580.  
  581. if (this.player.video.paused) {
  582. this.player.play();
  583. }
  584. }
  585. else {
  586. const fTime = this.formatTime(memoryTime);
  587. let memoryNode = document.createElement('div');
  588. memoryNode.setAttribute('class', 'memory-play-wrap');
  589. memoryNode.setAttribute('style', 'display: block;position: absolute;left: 33px;bottom: 66px;font-size: 15px;padding: 7px;border-radius: 3px;color: #fff;z-index:100;background: rgba(0,0,0,.5);');
  590. memoryNode.innerHTML = '上次播放到:' + fTime + '&nbsp;&nbsp;<a href="javascript:void(0);" class="play-jump" style="text-decoration: none;color: #06c;"> 跳转播放 &nbsp;</a><em class="close-btn" style="display: inline-block;width: 15px;height: 15px;vertical-align: middle;cursor: pointer;background: url(https://nd-static.bdstatic.com/m-static/disk-share/widget/pageModule/share-file-main/fileType/video/img/video-flash-closebtn_15f0e97.png) no-repeat;"></em>';
  591. this.player.container.insertBefore(memoryNode, null);
  592.  
  593. let memoryTimeout = setTimeout(() => {
  594. this.player.container.removeChild(memoryNode);
  595. }, 15000);
  596.  
  597. memoryNode.querySelector('.close-btn').onclick = () => {
  598. this.player.container.removeChild(memoryNode);
  599. clearTimeout(memoryTimeout);
  600. };
  601.  
  602. memoryNode.querySelector('.play-jump').onclick = () => {
  603. this.player.seek(memoryTime);
  604. this.player.container.removeChild(memoryNode);
  605. clearTimeout(memoryTimeout);
  606. }
  607. }
  608. }
  609. }
  610. }
  611.  
  612. getTime(e) {
  613. return localStorage.getItem("video_" + e) || 0;
  614. }
  615.  
  616. setTime(e, t, o) {
  617. e && t && (e = "video_" + e, t <= 60 || t + 120 >= o || 0 ? localStorage.removeItem(e) : localStorage.setItem(e, t));
  618. }
  619.  
  620. formatTime(seconds) {
  621. var secondTotal = Math.round(seconds)
  622. , hour = Math.floor(secondTotal / 3600)
  623. , minute = Math.floor((secondTotal - hour * 3600) / 60)
  624. , second = secondTotal - hour * 3600 - minute * 60;
  625. minute < 10 && (minute = "0" + minute);
  626. second < 10 && (second = "0" + second);
  627. return hour === 0 ? minute + ":" + second : hour + ":" + minute + ":" + second;
  628. }
  629. },
  630. class SkipPosition {
  631. constructor(player, obj) {
  632. this.player = player;
  633. this.file_id = this.player.options?.file?.file_id;
  634. this.timer = null;
  635.  
  636. Object.assign(this.player.user.storageName, { skipposition: "dplayer-skipposition", skipstarttime: "dplayer-skipstarttime", skipendtime: "dplayer-endtime" });
  637. Object.assign(this.player.user.default, { skipposition: 0, skipstarttime: 0, skipendtime: 0 });
  638. this.player.user.init();
  639. this.skipposition = this.player.user.get("skipposition");
  640. this.skipstarttime = this.player.user.get("skipstarttime");
  641. this.skipendtime = this.player.user.get("skipendtime");
  642.  
  643. this.player.template.skipPosition = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-skipposition"><span class="dplayer-label">跳过片头片尾</span><div class="dplayer-toggle"><input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle"><label for="dplayer-toggle"></label></div></div>');
  644. this.player.template.skipPositionToggle = obj.query('input', this.player.template.skipPosition);
  645. this.player.template.skipPositionToggle.checked = this.skipposition;
  646.  
  647. this.player.template.skipPositionBox = obj.insertAfter(this.player.template.settingBox, '<div class="dplayer-setting-skipposition-item" style="display: none;right: 155px;position: absolute;bottom: 50px;width: 150px;border-radius: 2px;background: rgba(28, 28, 28, 0.9);padding: 7px 0px;transition: all 0.3s ease-in-out 0s;overflow: hidden;z-index: 2;"><div class="dplayer-skipposition-item" style="padding: 5px 10px;box-sizing: border-box;cursor: pointer;position: relative;"><span class="dplayer-skipposition-label" title="双击设置当前时间为跳过片头时间" style="color: #eee;font-size: 13px;display: inline-block;vertical-align: middle;white-space: nowrap;">片头时间:</span><input type="number" style="width: 55px;height: 15px;top: 3px;font-size: 13px;border: 1px solid #fff;border-radius: 3px;text-align: center;" step="1" min="0" value="60"></div><div class="dplayer-skipposition-item" style="padding: 5px 10px;box-sizing: border-box;cursor: pointer;position: relative;"><span class="dplayer-skipposition-label" title="双击设置剩余时间为跳过片尾时间" style="color: #eee;font-size: 13px;display: inline-block;vertical-align: middle;white-space: nowrap;">片尾时间:</span><input type="number" style="width: 55px;height: 15px;top: 3px;font-size: 13px;border: 1px solid #fff;border-radius: 3px;text-align: center;" step="1" min="0" value="120"></div></div>');
  648. this.player.template.skipPositionItems = obj.queryAll('.dplayer-skipposition-item', this.player.template.skipPositionBox);
  649. this.player.template.jumpStartSpan = obj.query('span', this.player.template.skipPositionItems[0]);
  650. this.player.template.jumpStartInput = obj.query('input', this.player.template.skipPositionItems[0]);
  651. this.player.template.jumpEndSpan = obj.query('span', this.player.template.skipPositionItems[1]);
  652. this.player.template.jumpEndInput = obj.query('input', this.player.template.skipPositionItems[1]);
  653. this.player.template.jumpStartInput.value = this.skipstarttime;
  654. this.player.template.jumpEndInput.value = this.skipendtime;
  655.  
  656. this.player.template.jumpStartSpan.addEventListener('dblclick', (event) => {
  657. this.player.template.jumpStartInput.value = this.player.video.currentTime;
  658. this.skipstarttime = this.player.video.currentTime;
  659. });
  660. this.player.template.jumpStartInput.addEventListener('input', (event) => {
  661. this.skipstarttime = event.target.value * 1;
  662. this.player.user.set("skipstarttime", this.skipstarttime);
  663. });
  664. this.player.template.jumpEndSpan.addEventListener('dblclick', (event) => {
  665. this.skipendtime = this.player.video.duration - this.player.video.currentTime;
  666. this.player.template.jumpEndInput.value = this.skipendtime;
  667. });
  668. this.player.template.jumpEndInput.addEventListener('input', (event) => {
  669. this.skipendtime = event.target.value * 1;
  670. this.player.user.set("skipendtime", this.skipendtime);
  671. });
  672.  
  673. this.player.template.skipPosition.addEventListener('click', () => {
  674. this.skipposition = this.player.template.skipPositionToggle.checked = !this.player.template.skipPositionToggle.checked;
  675. this.player.user.set("skipposition", Number(this.skipposition));
  676. this.skipposition ? this.show() : this.hide();
  677. this.player.notice(`跳过片头片尾: ${this.skipposition ? '开启' : '关闭'}`);
  678. });
  679. this.player.template.skipPosition.addEventListener('mouseenter', () => {
  680. if (this.skipposition) {
  681. this.show();
  682. }
  683. });
  684.  
  685. this.player.template.mask.addEventListener('click', () => {
  686. this.hide();
  687. });
  688.  
  689. this.player.on('quality_end', () => {
  690. if (this.file_id !== this.player.options?.file?.file_id) {
  691. this.file_id = this.player.options?.file?.file_id;
  692. this.jumpStart();
  693. this.jumpEnd();
  694. }
  695. });
  696.  
  697. if (this.skipposition) {
  698. this.jumpStart();
  699. this.jumpEnd();
  700. }
  701. }
  702.  
  703. jumpStart() {
  704. if (this.skipposition && this.skipstarttime > this.player.video.currentTime) {
  705. this.player.video.currentTime = this.skipstarttime;
  706. }
  707. }
  708.  
  709. jumpEnd() {
  710. if (!this.timer) {
  711. this.timer = setInterval(() => {
  712. if (this.skipposition && this.skipendtime >= (this.player.video.duration - this.player.video.currentTime)) {
  713. this.player.video.currentTime = this.player.video.duration;
  714. clearInterval(this.timer);
  715. this.timer = null;
  716. }
  717. }, 3000);
  718. }
  719. }
  720.  
  721. show() {
  722. this.player.template.skipPositionBox.style.display = 'block';
  723. }
  724.  
  725. hide() {
  726. this.player.template.skipPositionBox.style.display = 'none';
  727. }
  728.  
  729. },
  730. class Subtitle {
  731. constructor(player, obj) {
  732. this.player = player;
  733. this.offset = 0;
  734. this.offsetStep = 1;
  735. this.color = this.get('color') || '#fff';
  736. this.bottom = this.get('bottom') || '40px';
  737. this.fontSize = this.get('fontSize') || '20px';
  738.  
  739. if (!player.events.type('subtitle_end')) {
  740. player.events.playerEvents.push('subtitle_end');
  741. }
  742. player.on('subtitle_end', () => {
  743. this.add(this.player.options.subtitles);
  744. });
  745. this.player.events.trigger('subtitle_start');
  746.  
  747. this.player.on('quality_end', () => {
  748. this.player.template.subtitle.innerHTML = `<p></p>`;
  749. if (this.player.options.subtitle.url.length && this.player.options.subtitles.length) {
  750. this.switch(this.player.options.subtitles[this.player.options.subtitle.index]);
  751. }
  752. else {
  753. this.player.events.trigger('subtitle_start');
  754. }
  755. });
  756.  
  757. this.player.on('episode_end', () => {
  758. this.clear();
  759.  
  760. this.style({
  761. color: this.color,
  762. bottom: this.bottom,
  763. fontSize: this.fontSize,
  764. });
  765. });
  766.  
  767. this.player.on('video_end', () => {
  768. this.style({
  769. color: this.color,
  770. bottom: this.bottom,
  771. fontSize: this.fontSize,
  772. });
  773. });
  774.  
  775. this.player.template.subtitleSettingBox = obj.append(this.player.template.controller, '<div class="dplayer-icons dplayer-comment-box subtitle-setting-box" style="bottom: 10px;left: auto;right: 400px !important;display: block;"><div class="dplayer-comment-setting-box"><div class="dplayer-comment-setting-color"><div class="dplayer-comment-setting-title">字幕颜色<button type="text" class="color-custom" style="line-height: 16px;font-size: 12px;top: 12px;right: 12px;color: #fff;background: rgba(28, 28, 28, 0.9);position: absolute;">自定义</button></div><label><input type="radio" name="dplayer-danmaku-color-1" value="#fff" checked=""><span style="background: #fff;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#e54256"><span style="background: #e54256"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#ffe133"><span style="background: #ffe133"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#64DD17"><span style="background: #64DD17"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#39ccff"><span style="background: #39ccff"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#D500F9"><span style="background: #D500F9"></span></label></div><div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕位置</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>上移</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0" checked=""><span>默认</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>下移</span></label></div><div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕大小</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>加大</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0"><span>默认</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>减小</span></label></div><div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕偏移<div style="margin-top: -30px;right: 14px;position: absolute;">偏移量<input type="number" class="subtitle-offset-step" style="height: 14px;width: 50px;margin-left: 4px;border: 1px solid #fff;border-radius: 3px;color: #fff;background: rgba(28, 28, 28, 0.9);text-align: center;" value="1" step="1" min="1"></div></div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>前移</span></label><label><span><input type="text" class="subtitle-offset" style="width: 94%;height: 14px;background: rgba(28, 28, 28, 0.9);border: 0px solid #fff;text-align: center;" value="0" title="双击恢复默认"></span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>后移</span></label></div><div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">更多字幕功能</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>本地字幕</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0"><span>待定</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>待定</span></label></div></div></div>');
  776. this.player.template.subtitleCommentSettingBox = obj.query('.dplayer-comment-setting-box', this.player.template.subtitleSettingBox);
  777. this.player.template.subtitleSetting = obj.append(obj.query('.dplayer-setting-origin-panel', this.player.template.settingBox), '<div class="dplayer-setting-item dplayer-setting-subtitle"><span class="dplayer-label">字幕设置</span><div class="dplayer-toggle"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32"><path d="M22 16l-10.105-10.6-1.895 1.987 8.211 8.613-8.211 8.612 1.895 1.988 8.211-8.613z"></path></svg></div></div>');
  778. this.player.template.mask.addEventListener('click', () => {
  779. this.hide();
  780. });
  781. this.player.template.subtitleSetting.addEventListener('click', () => {
  782. this.toggle();
  783. });
  784.  
  785. this.player.template.subtitleColorPicker = obj.append(this.player.template.container, '<input type="color" id="colorPicker">');
  786. this.player.template.subtitleColorCustom = obj.query('.color-custom', this.player.template.subtitleCommentSettingBox);
  787. this.player.template.subtitleColorCustom.addEventListener('click', () => {
  788. this.player.template.subtitleColorPicker.click();
  789. });
  790. this.player.template.subtitleColorPicker.addEventListener('input', (event) => {
  791. this.color = event.target.value;
  792. this.set("color", this.color);
  793. this.style({ color: this.color });
  794. });
  795.  
  796. this.player.template.subtitleSettingColor = obj.query('.dplayer-comment-setting-color', this.player.template.subtitleCommentSettingBox);
  797. this.player.template.subtitleSettingColor.addEventListener('click', (event) => {
  798. if (event.target.nodeName === "INPUT") {
  799. this.color = event.target.value;
  800. this.set("color", this.color);
  801. this.style({ color: this.color });
  802. }
  803. });
  804.  
  805. this.player.template.subtitleSettingItem = obj.queryAll('.dplayer-comment-setting-type', this.player.template.subtitleCommentSettingBox);
  806. this.player.template.subtitleSettingItem[0].addEventListener('click', (event) => {
  807. if (event.target.nodeName === "INPUT") {
  808. if (event.target.value == "1") {
  809. this.bottom = parseFloat(this.bottom) + 1 + this.bottom.replace(/[\d\.]+/, '');
  810. }
  811. else if (event.target.value == "2") {
  812. this.bottom = parseFloat(this.bottom) - 1 + this.bottom.replace(/[\d\.]+/, '');
  813. }
  814. else {
  815. this.bottom = '20px';
  816. }
  817. this.set('bottom', this.bottom);
  818. this.style({ bottom: this.bottom });
  819. }
  820. });
  821.  
  822. this.player.template.subtitleSettingItem[1].addEventListener('click', (event) => {
  823. if (event.target.nodeName === "INPUT") {
  824. if (event.target.value == "1") {
  825. this.fontSize = parseFloat(this.fontSize) + 1 + this.fontSize.replace(/[\d\.]+/, '');
  826. }
  827. else if (event.target.value == "2") {
  828. this.fontSize = parseFloat(this.fontSize) - 1 + this.fontSize.replace(/[\d\.]+/, '');
  829. }
  830. else {
  831. this.fontSize = '40px';
  832. }
  833. this.set("fontSize", this.fontSize);
  834. this.style({ fontSize: this.fontSize });
  835. }
  836. });
  837.  
  838. this.player.template.subtitleOffsetStep = obj.query('.subtitle-offset-step', this.player.template.subtitleSettingItem[2]);
  839. this.player.template.subtitleOffsetStep.addEventListener('input', (event) => {
  840. this.offsetStep = event.target.value * 1;
  841. });
  842.  
  843. this.player.template.subtitleOffset = obj.query('.subtitle-offset', this.player.template.subtitleSettingItem[2]);
  844. this.player.template.subtitleOffset.addEventListener('input', (event) => {
  845. this.offset = event.target.value * 1;
  846. this.subtitleOffset();
  847. });
  848. this.player.template.subtitleOffset.addEventListener('dblclick', (event) => {
  849. if (this.offset != 0) {
  850. this.offset = 0;
  851. event.target.value = 0;
  852. this.subtitleOffset();
  853. }
  854. });
  855.  
  856. this.player.template.subtitleSettingItem[2].addEventListener('click', (event) => {
  857. if (event.target.nodeName === "INPUT") {
  858. if (event.target.type === "radio") {
  859. let value = this.player.template.subtitleOffset.value *= 1;
  860. if (event.target.value == "1") {
  861. value += this.offsetStep || 1;
  862. }
  863. else if (event.target.value == "2") {
  864. value -= this.offsetStep || 1;
  865. }
  866. else {
  867. value = 0;
  868. }
  869. this.offset = value;
  870. this.player.template.subtitleOffset.value = value;
  871. this.subtitleOffset();
  872. }
  873. }
  874. });
  875.  
  876. this.player.template.subtitleLocalFile = obj.append(this.player.template.container, '<input class="subtitleLocalFile" type="file" accept="webvtt,.vtt,.srt,.ssa,.ass" style="display: none;">');
  877. this.player.template.subtitleSettingItem[3].addEventListener('click', (event) => {
  878. if (event.target.nodeName === "INPUT") {
  879. if (event.target.value == "1") {
  880. this.player.template.subtitleLocalFile.click();
  881. this.hide();
  882. }
  883. }
  884. });
  885. this.player.template.subtitleLocalFile.addEventListener("change", (event) => {
  886. if (event.target.files.length) {
  887. const file = event.target.files[0];
  888. const file_ext = file.name.split(".").pop().toLowerCase();
  889. this.blobToText(file).then((text) => {
  890. let subtitleOption = {};
  891. subtitleOption.url = '';
  892. subtitleOption.sarr = this.subParser(text, file_ext);
  893. subtitleOption.lang = this.getlangBySarr(subtitleOption.sarr);
  894. subtitleOption.name = subtitleOption.name || this.langToLabel(subtitleOption.lang);
  895. this.add([subtitleOption]);
  896. this.switch(subtitleOption);
  897. });
  898. }
  899. event.target.value = "";
  900. });
  901.  
  902. this.style({
  903. color: this.color,
  904. bottom: this.bottom,
  905. fontSize: this.fontSize,
  906. });
  907. }
  908.  
  909. add(sublist) {
  910. if (!(Array.isArray(sublist) && sublist.length)) {
  911. return;
  912. }
  913. if (!(this.player.template.subtitlesBox && this.player.template.subtitlesItem.length)) {
  914. return;
  915. }
  916.  
  917. const lastItemIndex = this.player.template.subtitlesItem.length - 1;
  918. sublist.forEach((item, index) => {
  919. const i = lastItemIndex + index;
  920. this.player.options.subtitle.url.splice(i, 0, item);
  921.  
  922. let itemNode = document.createElement('div');
  923. itemNode.setAttribute('class', 'dplayer-subtitles-item');
  924. itemNode.innerHTML = '<span class="dplayer-label">' + (item.name + ' ' + (item.language || item.lang || "")) + '</span>';
  925. this.player.template.subtitlesBox.insertBefore(itemNode, this.player.template.subtitlesBox.childNodes[i]);
  926.  
  927. itemNode.addEventListener('click', (event) => {
  928. this.player.subtitles.hide();
  929. if (this.player.options.subtitle.index !== i) {
  930. this.player.options.subtitle.index = i;
  931.  
  932. this.player.template.subtitle.innerHTML = `<p></p>`;
  933. this.switch(item);
  934.  
  935. if (this.player.template.subtitle.classList.contains('dplayer-subtitle-hide')) {
  936. this.player.subtitles.subContainerShow();
  937. }
  938. }
  939. });
  940. });
  941. this.player.template.subtitlesItem = this.player.template.subtitlesBox.querySelectorAll('.dplayer-subtitles-item');
  942.  
  943. if (!(this.player.video.textTracks.length && this.player.video.textTracks[0])?.cues) {
  944. this.player.options.subtitle.index = this.player.options.subtitles.findIndex((item) => {
  945. return ['cho', 'chi'].includes(item.language);
  946. });
  947. if (this.player.options.subtitle.index < 0) {
  948. this.player.options.subtitle.index = 0;
  949. }
  950. this.switch(this.player.options.subtitle.url[this.player.options.subtitle.index]);
  951. }
  952. }
  953.  
  954. switch(newOption = {}) {
  955. return this.initCues(newOption).then((subArr) => {
  956. if (newOption.name) {
  957. //this.player.notice(`切换字幕: ${newOption.name}`);
  958. }
  959. });
  960. }
  961.  
  962. restart() {
  963. this.clear();
  964. this.add(this.player.options.subtitles);
  965. }
  966.  
  967. clear() {
  968. this.player.template.subtitle.innerHTML = `<p></p>`;
  969. this.player.options.subtitles = [];
  970. this.player.options.subtitle.url = [];
  971. for (let i = 0; i < this.player.template.subtitlesItem.length - 1; i++) {
  972. this.player.template.subtitlesBox.removeChild(this.player.template.subtitlesItem[i]);
  973. }
  974. this.player.template.subtitlesItem = this.player.template.subtitlesBox.querySelectorAll('.dplayer-subtitles-item');
  975. }
  976.  
  977. initCues(subtitleOption) {
  978. return this.urlToArray(subtitleOption).then((subtitleOption) => {
  979. return this.createTrack(subtitleOption);
  980. });
  981. }
  982.  
  983. urlToArray(subtitleOption) {
  984. if (Array.isArray(subtitleOption?.sarr) && subtitleOption.sarr.length) {
  985. return Promise.resolve(subtitleOption);
  986. }
  987. else {
  988. const url = subtitleOption?.url || subtitleOption?.download_url || subtitleOption?.uri || subtitleOption?.surl;
  989. const extension = subtitleOption.sext || subtitleOption.file_extension;
  990. if (url) {
  991. return this.requestFile(url).then((text) => {
  992. subtitleOption.sarr = this.subParser(text, extension, subtitleOption.delay || 0);
  993. subtitleOption.lang = subtitleOption.lang || this.getlangBySarr(subtitleOption.sarr);
  994. subtitleOption.name = subtitleOption.name || this.langToLabel(subtitleOption.lang);
  995. return subtitleOption;
  996. });
  997. }
  998. else {
  999. return Promise.reject();
  1000. }
  1001. }
  1002. }
  1003.  
  1004. createTrack(subtitleOption) {
  1005. const { video } = this.player;
  1006. const textTracks = video.textTracks;
  1007. const textTrack = textTracks[0];
  1008.  
  1009. textTrack.mode === "hidden" || (textTrack.mode = "hidden");
  1010. if (textTrack.cues && textTrack.cues.length) {
  1011. for (let i = textTrack.cues.length - 1; i >= 0; i--) {
  1012. textTrack.removeCue(textTrack.cues[i]);
  1013. }
  1014. }
  1015.  
  1016. subtitleOption.sarr.forEach(function (item, index) {
  1017. const textTrackCue = new VTTCue(item.startTime, item.endTime, item.text);
  1018. textTrackCue.id = item.index;
  1019. textTrack.addCue(textTrackCue);
  1020. });
  1021. }
  1022.  
  1023. requestFile(url) {
  1024. return fetch(url, {
  1025. referrer: "https://www.aliyundrive.com/",
  1026. referrerPolicy: "origin",
  1027. body: null,
  1028. method: "GET",
  1029. mode: "cors",
  1030. credentials: "omit"
  1031. }).then(data => data.blob()).then(blob => {
  1032. return this.blobToText(blob)
  1033. });
  1034. };
  1035.  
  1036. blobToText(blob) {
  1037. return new Promise(function (resolve, reject) {
  1038. var reader = new FileReader();
  1039. reader.readAsText(blob, "UTF-8");
  1040. reader.onload = function (e) {
  1041. var result = reader.result;
  1042. if (result.indexOf("�") > -1 && !reader.markGBK) {
  1043. reader.markGBK = true;
  1044. return reader.readAsText(blob, "GBK");
  1045. }
  1046. else if (result.indexOf("") > -1 && !reader.markBIG5) {
  1047. reader.markBIG5 = true;
  1048. return reader.readAsText(blob, "BIG5");
  1049. }
  1050. resolve(result);
  1051. };
  1052. reader.onerror = function (error) {
  1053. reject(error);
  1054. };
  1055. });
  1056. }
  1057.  
  1058. subParser(stext, sext, delay = 0) {
  1059. if (!sext) {
  1060. sext = (stext.indexOf("->") > 0 ? "srt" : stext.indexOf("Dialogue:") > 0 ? "ass" : "").toLowerCase();
  1061. }
  1062. var regex, data = [], items = [];
  1063. switch (sext) {
  1064. case "webvtt":
  1065. case "vtt":
  1066. case "srt":
  1067. stext = stext.replace(/\r/g, "");
  1068. regex = /(\d+)?\n?(\d{0,2}:?\d{2}:\d{2}.\d{3}) -?-> (\d{0,2}:?\d{2}:\d{2}.\d{3})/g;
  1069. data = stext.split(regex);
  1070. data.shift();
  1071. for (let i = 0; i < data.length; i += 4) {
  1072. items.push({
  1073. index: items.length,
  1074. startTime: parseTimestamp(data[i + 1]) + +delay,
  1075. endTime: parseTimestamp(data[i + 2]) + +delay,
  1076. text: data[i + 3].trim().replace(/(\\N|\\n)/g, "\n").replace(/{.*?}/g, "").replace(/[a-z]+\:.*\d+\.\d+\%\s/, "")
  1077. });
  1078. }
  1079. return items;
  1080. case "ssa":
  1081. case "ass":
  1082. stext = stext.replace(/\r\n/g, "");
  1083. regex = /Dialogue: .*?\d+,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),.*?,\d+,\d+,\d+,.*?,/g;
  1084. data = stext.split(regex);
  1085. data.shift();
  1086. for (let i = 0; i < data.length; i += 3) {
  1087. items.push({
  1088. index: items.length,
  1089. startTime: parseTimestamp(data[i]) + +delay,
  1090. endTime: parseTimestamp(data[i + 1]) + +delay,
  1091. text: data[i + 2].trim().replace(/(\\N|\\n)/g, "\n").replace(/{.*?}/g, "")
  1092. });
  1093. }
  1094. return items;
  1095. default:
  1096. console.error("未知字幕格式,无法解析");
  1097. console.info(sext, stext);
  1098. return items;
  1099. }
  1100.  
  1101. function parseTimestamp(e) {
  1102. var t = e.split(":")
  1103. , n = parseFloat(t.length > 0 ? t.pop().replace(/,/g, ".") : "00.000") || 0
  1104. , r = parseFloat(t.length > 0 ? t.pop() : "00") || 0;
  1105. return 3600 * (parseFloat(t.length > 0 ? t.pop() : "00") || 0) + 60 * r + n;
  1106. }
  1107. };
  1108.  
  1109. getlangBySarr(sarr) {
  1110. var t = [
  1111. sarr[parseInt(sarr.length / 3)].text,
  1112. sarr[parseInt(sarr.length / 2)].text,
  1113. sarr[parseInt(sarr.length / 3 * 2)].text
  1114. ].join("").replace(/[<bi\/>\r?\n]*/g, "");
  1115.  
  1116. var e = "eng"
  1117. , i = (t.match(/[\u4e00-\u9fa5]/g) || []).length / t.length;
  1118. (t.match(/[\u3020-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\u31F0-\u31FF]/g) || []).length / t.length > .03 ? e = "jpn" : i > .1 && (e = "zho");
  1119. return e;
  1120. };
  1121.  
  1122. langToLabel(lang) {
  1123. return {
  1124. chi: "中文字幕",
  1125. zho: "中文字幕",
  1126. eng: "英文字幕",
  1127. en: "英文字幕",
  1128. jpn: "日文字幕",
  1129. "zh-CN": "简体中文",
  1130. "zh-TW": "繁体中文"
  1131. }[lang] || "未知语言";
  1132. };
  1133.  
  1134. subtitleOffset() {
  1135. const { video, subtitle, events } = this.player;
  1136. const textTrack = video.textTracks[0];
  1137. if (textTrack && textTrack.cues) {
  1138. const cues = Array.from(textTrack.cues);
  1139. const sarr = this.player.options.subtitle.url.find((item) => {
  1140. return item.sarr && item.sarr[parseInt(item.sarr.length / 2)].text === cues[parseInt(cues.length / 2)].text;
  1141. })?.sarr;
  1142. if (!sarr) {
  1143. return;
  1144. }
  1145.  
  1146. for (let index = 0; index < cues.length; index++) {
  1147. const cue = cues[index];
  1148. cue.startTime = sarr[index].startTime + this.offset;
  1149. cue.endTime = sarr[index].endTime + this.offset;
  1150. }
  1151.  
  1152. events.trigger('subtitle_change');
  1153.  
  1154. this.player.notice(`字幕偏移: ${this.offset} 秒`);
  1155. }
  1156. else {
  1157. this.offset = 0;
  1158. }
  1159. }
  1160.  
  1161. toggle() {
  1162. if (this.player.template.subtitleCommentSettingBox.classList.contains('dplayer-comment-setting-open')) {
  1163. this.hide();
  1164. } else {
  1165. this.show();
  1166. }
  1167. }
  1168.  
  1169. show() {
  1170. this.player.template.subtitleCommentSettingBox.classList.add('dplayer-comment-setting-open');
  1171. this.player.template.mask.classList.add('dplayer-mask-show');
  1172. }
  1173.  
  1174. hide() {
  1175. this.player.template.subtitleCommentSettingBox.classList.remove('dplayer-comment-setting-open');
  1176. this.player.template.settingBox.classList.remove('dplayer-setting-box-open');
  1177. this.player.template.mask.classList.remove('dplayer-mask-show');
  1178. }
  1179.  
  1180. get(key) {
  1181. return localStorage.getItem("dplayer-subtitle-" + key);
  1182. }
  1183.  
  1184. set(key, value) {
  1185. key && value && localStorage.setItem("dplayer-subtitle-" + key, value);
  1186. }
  1187.  
  1188. style(key, value) {
  1189. const { subtitle } = this.player.template;
  1190. if (typeof key === 'object') {
  1191. for (const k in key) {
  1192. subtitle.style[k] = key[k];
  1193. }
  1194. return subtitle;
  1195. }
  1196. subtitle.style[key] = value;
  1197. return subtitle;
  1198. }
  1199. },
  1200. class Appreciation {
  1201. constructor(player, obj) {
  1202. this.player = player;
  1203. this.now = Date.now();
  1204. this.localforage = window.localforage;
  1205.  
  1206. const { contextmenu,
  1207. container: { offsetWidth, offsetHeight }
  1208. } = this.player;
  1209.  
  1210. }
  1211. },
  1212. class doHotKey {
  1213. constructor(player) {
  1214. this.player = player;
  1215.  
  1216. this.player.template.videoWrap.addEventListener('dblclick', (event) => {
  1217. this.player.fullScreen.toggle();
  1218. });
  1219.  
  1220. document.addEventListener("wheel", (event) => {
  1221. if (this.player.focus) {
  1222. event = event || window.event;
  1223. var o, t = this.player;
  1224. if (event.deltaY < 0) {
  1225. o = t.volume() + .01;
  1226. t.volume(o);
  1227. } else if (event.deltaY > 0) {
  1228. o = t.volume() - .01;
  1229. t.volume(o);
  1230. }
  1231. }
  1232. });
  1233. }
  1234. },
  1235. ]);