DPlayer.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. import './DPlayer.scss';
  2. import utils, {isMobile} from './utils';
  3. import svg from './svg';
  4. import handleOption from './option';
  5. import i18n from './i18n';
  6. import html from './html';
  7. let index = 0;
  8. class DPlayer {
  9. /**
  10. * DPlayer constructor function
  11. *
  12. * @param {Object} option - See README
  13. * @constructor
  14. */
  15. constructor (option) {
  16. this.option = handleOption(option);
  17. this.option.element.classList.add('dplayer');
  18. if (this.option.video.quality) {
  19. this.qualityIndex = this.option.video.defaultQuality;
  20. this.quality = this.option.video.quality[this.option.video.defaultQuality];
  21. }
  22. this.tran = new i18n(this.option.lang).tran;
  23. /**
  24. * Update progress bar, including loading progress bar and play progress bar
  25. *
  26. * @param {String} type - Point out which bar it is, should be played loaded or volume
  27. * @param {Number} percentage
  28. * @param {String} direction - Point out the direction of this bar, Should be height or width
  29. */
  30. this.updateBar = (type, percentage, direction) => {
  31. percentage = percentage > 0 ? percentage : 0;
  32. percentage = percentage < 1 ? percentage : 1;
  33. bar[type + 'Bar'].style[direction] = percentage * 100 + '%';
  34. };
  35. // define DPlayer events
  36. const eventTypes = ['play', 'pause', 'canplay', 'playing', 'ended', 'error'];
  37. this.event = {};
  38. for (let i = 0; i < eventTypes.length; i++) {
  39. this.event[eventTypes[i]] = [];
  40. }
  41. this.trigger = (type) => {
  42. for (let i = 0; i < this.event[type].length; i++) {
  43. this.event[type][i]();
  44. }
  45. };
  46. this.element = this.option.element;
  47. if (!this.option.danmaku) {
  48. this.element.classList.add('dplayer-no-danmaku');
  49. }
  50. if (isMobile) {
  51. this.element.classList.add('dplayer-mobile');
  52. }
  53. this.element.innerHTML = html.main(option, index, this.tran);
  54. // arrow style
  55. this.arrow = this.element.offsetWidth <= 500;
  56. if (this.arrow) {
  57. const arrowStyle = document.createElement('style');
  58. arrowStyle.innerHTML = `.dplayer .dplayer-danmaku{font-size:18px}`;
  59. document.head.appendChild(arrowStyle);
  60. }
  61. // get this video manager
  62. this.video = this.element.getElementsByClassName('dplayer-video-current')[0];
  63. this.initVideo();
  64. this.bezel = this.element.getElementsByClassName('dplayer-bezel-icon')[0];
  65. this.bezel.addEventListener('animationend', () => {
  66. this.bezel.classList.remove('dplayer-bezel-transition');
  67. });
  68. // play and pause button
  69. this.playButton = this.element.getElementsByClassName('dplayer-play-icon')[0];
  70. this.paused = true;
  71. this.playButton.addEventListener('click', () => {
  72. this.toggle();
  73. });
  74. const videoWrap = this.element.getElementsByClassName('dplayer-video-wrap')[0];
  75. const conMask = this.element.getElementsByClassName('dplayer-controller-mask')[0];
  76. if (!isMobile) {
  77. videoWrap.addEventListener('click', () => {
  78. this.toggle();
  79. });
  80. conMask.addEventListener('click', () => {
  81. this.toggle();
  82. });
  83. }
  84. else {
  85. const toggleController = () => {
  86. if (this.element.classList.contains('dplayer-hide-controller')) {
  87. this.element.classList.remove('dplayer-hide-controller');
  88. }
  89. else {
  90. this.element.classList.add('dplayer-hide-controller');
  91. }
  92. };
  93. videoWrap.addEventListener('click', toggleController);
  94. conMask.addEventListener('click', toggleController);
  95. }
  96. const bar = {};
  97. bar.playedBar = this.element.getElementsByClassName('dplayer-played')[0];
  98. bar.loadedBar = this.element.getElementsByClassName('dplayer-loaded')[0];
  99. const pbar = this.element.getElementsByClassName('dplayer-bar-wrap')[0];
  100. const pbarTimeTips = this.element.getElementsByClassName('dplayer-bar-time')[0];
  101. let barWidth;
  102. let lastPlayPos = 0;
  103. let currentPlayPos = 0;
  104. let bufferingDetected = false;
  105. this.danmakuTime = false;
  106. this.playedTime = false;
  107. window.requestAnimationFrame = (() =>
  108. window.requestAnimationFrame ||
  109. window.webkitRequestAnimationFrame ||
  110. window.mozRequestAnimationFrame ||
  111. window.oRequestAnimationFrame ||
  112. window.msRequestAnimationFrame ||
  113. function (callback) {
  114. window.setTimeout(callback, 1000 / 60);
  115. }
  116. )();
  117. const setCheckLoadingTime = () => {
  118. this.checkLoading = setInterval(() => {
  119. // whether the video is buffering
  120. currentPlayPos = this.video.currentTime;
  121. if (!bufferingDetected
  122. && currentPlayPos < lastPlayPos + 0.01
  123. && !this.video.paused) {
  124. this.element.classList.add('dplayer-loading');
  125. bufferingDetected = true;
  126. }
  127. if (bufferingDetected
  128. && currentPlayPos > lastPlayPos + 0.01
  129. && !this.video.paused) {
  130. this.element.classList.remove('dplayer-loading');
  131. bufferingDetected = false;
  132. }
  133. lastPlayPos = currentPlayPos;
  134. }, 100);
  135. };
  136. const clearCheckLoadingTime = () => {
  137. clearInterval(this.checkLoading);
  138. };
  139. this.animationFrame = () => {
  140. if (this.playedTime) {
  141. this.updateBar('played', this.video.currentTime / this.video.duration, 'width');
  142. this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = utils.secondToTime(this.video.currentTime);
  143. this.trigger('playing');
  144. }
  145. if (this.danmakuTime && this.option.danmaku && showdan && this.dan) {
  146. let item = this.dan[this.danIndex];
  147. const danmakus = [];
  148. while (item && this.video.currentTime > parseFloat(item.time)) {
  149. danmakus.push(item);
  150. item = this.dan[++this.danIndex];
  151. }
  152. this.pushDanmaku(danmakus);
  153. }
  154. this.requestID = window.requestAnimationFrame(this.animationFrame);
  155. };
  156. this.requestID = window.requestAnimationFrame(this.animationFrame);
  157. this.setTime = (type) => {
  158. if (!type) {
  159. this.danmakuTime = true;
  160. this.playedTime = true;
  161. setCheckLoadingTime();
  162. }
  163. else {
  164. this[`${type}Time`] = true;
  165. if (type === 'played') {
  166. setCheckLoadingTime();
  167. }
  168. }
  169. };
  170. this.clearTime = (type) => {
  171. if (!type) {
  172. this.danmakuTime = false;
  173. this.playedTime = false;
  174. clearCheckLoadingTime();
  175. }
  176. else {
  177. this[`${type}Time`] = false;
  178. if (type === 'played') {
  179. clearCheckLoadingTime();
  180. }
  181. }
  182. };
  183. pbar.addEventListener('click', (event) => {
  184. const e = event || window.event;
  185. barWidth = pbar.clientWidth;
  186. let percentage = (e.clientX - utils.getElementViewLeft(pbar)) / barWidth;
  187. percentage = percentage > 0 ? percentage : 0;
  188. percentage = percentage < 1 ? percentage : 1;
  189. this.updateBar('played', percentage, 'width');
  190. this.seek(parseFloat(bar.playedBar.style.width) / 100 * this.video.duration);
  191. });
  192. this.isTipsShow = false;
  193. this.timeTipsHandler = this.timeTipsHandler(
  194. pbar, pbarTimeTips).bind(this);
  195. pbar.addEventListener('mousemove', this.timeTipsHandler);
  196. pbar.addEventListener('mouseover', this.timeTipsHandler);
  197. pbar.addEventListener('mouseenter', this.timeTipsHandler);
  198. pbar.addEventListener('mouseout', this.timeTipsHandler);
  199. pbar.addEventListener('mouseleave', this.timeTipsHandler);
  200. const thumbMove = (event) => {
  201. const e = event || window.event;
  202. let percentage = (e.clientX - utils.getElementViewLeft(pbar)) / barWidth;
  203. percentage = percentage > 0 ? percentage : 0;
  204. percentage = percentage < 1 ? percentage : 1;
  205. this.updateBar('played', percentage, 'width');
  206. this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = utils.secondToTime(percentage * this.video.duration);
  207. };
  208. const thumbUp = () => {
  209. document.removeEventListener('mouseup', thumbUp);
  210. document.removeEventListener('mousemove', thumbMove);
  211. this.seek(parseFloat(bar.playedBar.style.width) / 100 * this.video.duration);
  212. this.setTime();
  213. };
  214. pbar.addEventListener('mousedown', () => {
  215. barWidth = pbar.clientWidth;
  216. this.clearTime();
  217. document.addEventListener('mousemove', thumbMove);
  218. document.addEventListener('mouseup', thumbUp);
  219. });
  220. /**
  221. * control volume
  222. */
  223. bar.volumeBar = this.element.getElementsByClassName('dplayer-volume-bar-inner')[0];
  224. const volumeEle = this.element.getElementsByClassName('dplayer-volume')[0];
  225. const volumeBarWrapWrap = this.element.getElementsByClassName('dplayer-volume-bar-wrap')[0];
  226. const volumeBarWrap = this.element.getElementsByClassName('dplayer-volume-bar')[0];
  227. const volumeicon = this.element.getElementsByClassName('dplayer-volume-icon')[0];
  228. const vWidth = 35;
  229. this.switchVolumeIcon = () => {
  230. const volumeicon = this.element.getElementsByClassName('dplayer-volume-icon')[0];
  231. if (this.video.volume >= 0.8) {
  232. volumeicon.innerHTML = svg('volume-up');
  233. }
  234. else if (this.video.volume > 0) {
  235. volumeicon.innerHTML = svg('volume-down');
  236. }
  237. else {
  238. volumeicon.innerHTML = svg('volume-off');
  239. }
  240. };
  241. const volumeMove = (event) => {
  242. const e = event || window.event;
  243. const percentage = (e.clientX - utils.getElementViewLeft(volumeBarWrap) - 5.5) / vWidth;
  244. this.volume(percentage);
  245. };
  246. const volumeUp = () => {
  247. document.removeEventListener('mouseup', volumeUp);
  248. document.removeEventListener('mousemove', volumeMove);
  249. volumeEle.classList.remove('dplayer-volume-active');
  250. };
  251. volumeBarWrapWrap.addEventListener('click', (event) => {
  252. const e = event || window.event;
  253. const percentage = (e.clientX - utils.getElementViewLeft(volumeBarWrap) - 5.5) / vWidth;
  254. this.volume(percentage);
  255. });
  256. volumeBarWrapWrap.addEventListener('mousedown', () => {
  257. document.addEventListener('mousemove', volumeMove);
  258. document.addEventListener('mouseup', volumeUp);
  259. volumeEle.classList.add('dplayer-volume-active');
  260. });
  261. volumeicon.addEventListener('click', () => {
  262. if (this.video.muted) {
  263. this.video.muted = false;
  264. this.switchVolumeIcon();
  265. this.updateBar('volume', this.video.volume, 'width');
  266. }
  267. else {
  268. this.video.muted = true;
  269. volumeicon.innerHTML = svg('volume-off');
  270. this.updateBar('volume', 0, 'width');
  271. }
  272. });
  273. /**
  274. * auto hide controller
  275. */
  276. this.hideTime = 0;
  277. if (!isMobile) {
  278. const hideController = () => {
  279. this.element.classList.remove('dplayer-hide-controller');
  280. clearTimeout(this.hideTime);
  281. this.hideTime = setTimeout(() => {
  282. if (this.video.played.length) {
  283. this.element.classList.add('dplayer-hide-controller');
  284. closeSetting();
  285. closeComment();
  286. }
  287. }, 2000);
  288. };
  289. this.element.addEventListener('mousemove', hideController);
  290. this.element.addEventListener('click', hideController);
  291. }
  292. /**
  293. * setting
  294. */
  295. this.danOpacity = localStorage.getItem('DPlayer-opacity') || 0.7;
  296. const settingHTML = html.setting(this.tran);
  297. // toggle setting box
  298. const settingIcon = this.element.getElementsByClassName('dplayer-setting-icon')[0];
  299. const settingBox = this.element.getElementsByClassName('dplayer-setting-box')[0];
  300. const mask = this.element.getElementsByClassName('dplayer-mask')[0];
  301. settingBox.innerHTML = settingHTML.original;
  302. const closeSetting = () => {
  303. if (settingBox.classList.contains('dplayer-setting-box-open')) {
  304. settingBox.classList.remove('dplayer-setting-box-open');
  305. mask.classList.remove('dplayer-mask-show');
  306. setTimeout(() => {
  307. settingBox.classList.remove('dplayer-setting-box-narrow');
  308. settingBox.innerHTML = settingHTML.original;
  309. settingEvent();
  310. }, 300);
  311. }
  312. };
  313. const openSetting = () => {
  314. settingBox.classList.add('dplayer-setting-box-open');
  315. mask.classList.add('dplayer-mask-show');
  316. };
  317. mask.addEventListener('click', () => {
  318. closeSetting();
  319. });
  320. settingIcon.addEventListener('click', () => {
  321. openSetting();
  322. });
  323. this.loop = this.option.loop;
  324. const danContainer = this.element.getElementsByClassName('dplayer-danmaku')[0];
  325. let showdan = true;
  326. const settingEvent = () => {
  327. // loop control
  328. const loopEle = this.element.getElementsByClassName('dplayer-setting-loop')[0];
  329. const loopToggle = loopEle.getElementsByClassName('dplayer-toggle-setting-input')[0];
  330. loopToggle.checked = this.loop;
  331. loopEle.addEventListener('click', () => {
  332. loopToggle.checked = !loopToggle.checked;
  333. if (loopToggle.checked) {
  334. this.loop = true;
  335. }
  336. else {
  337. this.loop = false;
  338. }
  339. closeSetting();
  340. });
  341. // show danmaku control
  342. const showDanEle = this.element.getElementsByClassName('dplayer-setting-showdan')[0];
  343. const showDanToggle = showDanEle.getElementsByClassName('dplayer-showdan-setting-input')[0];
  344. showDanToggle.checked = showdan;
  345. showDanEle.addEventListener('click', () => {
  346. showDanToggle.checked = !showDanToggle.checked;
  347. if (showDanToggle.checked) {
  348. showdan = true;
  349. if (this.option.danmaku) {
  350. for (let i = 0; i < this.dan.length; i++) {
  351. if (this.dan[i].time >= this.video.currentTime) {
  352. this.danIndex = i;
  353. break;
  354. }
  355. this.danIndex = this.dan.length;
  356. }
  357. if (!this.paused) {
  358. this.setTime('danmaku');
  359. }
  360. }
  361. }
  362. else {
  363. showdan = false;
  364. if (this.option.danmaku) {
  365. this.clearTime('danmaku');
  366. danContainer.innerHTML = '';
  367. this.danTunnel = {
  368. right: {},
  369. top: {},
  370. bottom: {}
  371. };
  372. }
  373. }
  374. closeSetting();
  375. });
  376. // speed control
  377. const speedEle = this.element.getElementsByClassName('dplayer-setting-speed')[0];
  378. speedEle.addEventListener('click', () => {
  379. settingBox.classList.add('dplayer-setting-box-narrow');
  380. settingBox.innerHTML = settingHTML.speed;
  381. const speedItem = settingBox.getElementsByClassName('dplayer-setting-speed-item');
  382. for (let i = 0; i < speedItem.length; i++) {
  383. speedItem[i].addEventListener('click', () => {
  384. this.video.playbackRate = speedItem[i].dataset.speed;
  385. closeSetting();
  386. });
  387. }
  388. });
  389. if (this.option.danmaku) {
  390. // danmaku opacity
  391. bar.danmakuBar = this.element.getElementsByClassName('dplayer-danmaku-bar-inner')[0];
  392. const danmakuBarWrapWrap = this.element.getElementsByClassName('dplayer-danmaku-bar-wrap')[0];
  393. const danmakuBarWrap = this.element.getElementsByClassName('dplayer-danmaku-bar')[0];
  394. const danmakuSettingBox = this.element.getElementsByClassName('dplayer-setting-danmaku')[0];
  395. const dWidth = 130;
  396. this.updateBar('danmaku', this.danOpacity, 'width');
  397. const danmakuMove = (event) => {
  398. const e = event || window.event;
  399. let percentage = (e.clientX - utils.getElementViewLeft(danmakuBarWrap)) / dWidth;
  400. percentage = percentage > 0 ? percentage : 0;
  401. percentage = percentage < 1 ? percentage : 1;
  402. this.updateBar('danmaku', percentage, 'width');
  403. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  404. for (let i = 0; i < items.length; i++) {
  405. items[i].style.opacity = percentage;
  406. }
  407. this.danOpacity = percentage;
  408. localStorage.setItem('DPlayer-opacity', this.danOpacity);
  409. };
  410. const danmakuUp = () => {
  411. document.removeEventListener('mouseup', danmakuUp);
  412. document.removeEventListener('mousemove', danmakuMove);
  413. danmakuSettingBox.classList.remove('dplayer-setting-danmaku-active');
  414. };
  415. danmakuBarWrapWrap.addEventListener('click', (event) => {
  416. const e = event || window.event;
  417. let percentage = (e.clientX - utils.getElementViewLeft(danmakuBarWrap)) / dWidth;
  418. percentage = percentage > 0 ? percentage : 0;
  419. percentage = percentage < 1 ? percentage : 1;
  420. this.updateBar('danmaku', percentage, 'width');
  421. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  422. for (let i = 0; i < items.length; i++) {
  423. items[i].style.opacity = percentage;
  424. }
  425. this.danOpacity = percentage;
  426. localStorage.setItem('DPlayer-opacity', this.danOpacity);
  427. });
  428. danmakuBarWrapWrap.addEventListener('mousedown', () => {
  429. document.addEventListener('mousemove', danmakuMove);
  430. document.addEventListener('mouseup', danmakuUp);
  431. danmakuSettingBox.classList.add('dplayer-setting-danmaku-active');
  432. });
  433. }
  434. };
  435. settingEvent();
  436. // set duration time
  437. if (this.video.duration !== 1) { // compatibility: Android browsers will output 1 at first
  438. this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = this.video.duration ? utils.secondToTime(this.video.duration) : '00:00';
  439. }
  440. // danmaku
  441. this.danTunnel = {
  442. right: {},
  443. top: {},
  444. bottom: {}
  445. };
  446. const measureStyle = getComputedStyle(this.element.getElementsByClassName('dplayer-danmaku-item')[0], null);
  447. const context = document.createElement('canvas').getContext('2d');
  448. context.font = measureStyle.getPropertyValue('font-size') + ' ' + measureStyle.getPropertyValue('font-family');
  449. this.danmakuMeasure = (text) => context.measureText(text).width;
  450. if (this.option.danmaku) {
  451. this.danIndex = 0;
  452. this.readDanmaku();
  453. }
  454. else {
  455. // autoplay
  456. if (this.option.autoplay && !isMobile) {
  457. this.play();
  458. }
  459. else if (isMobile) {
  460. this.pause();
  461. }
  462. }
  463. /**
  464. * comment
  465. */
  466. const commentInput = this.element.getElementsByClassName('dplayer-comment-input')[0];
  467. const commentIcon = this.element.getElementsByClassName('dplayer-comment-icon')[0];
  468. const commentBox = this.element.getElementsByClassName('dplayer-comment-box')[0];
  469. const commentSettingIcon = this.element.getElementsByClassName('dplayer-comment-setting-icon')[0];
  470. const commentSettingBox = this.element.getElementsByClassName('dplayer-comment-setting-box')[0];
  471. const commentSendIcon = this.element.getElementsByClassName('dplayer-send-icon')[0];
  472. const htmlEncode = (str) => str.
  473. replace(/&/g, "&amp;").
  474. replace(/</g, "&lt;").
  475. replace(/>/g, "&gt;").
  476. replace(/"/g, "&quot;").
  477. replace(/'/g, "&#x27;").
  478. replace(/\//g, "&#x2f;");
  479. const sendComment = () => {
  480. commentInput.blur();
  481. // text can't be empty
  482. if (!commentInput.value.replace(/^\s+|\s+$/g, '')) {
  483. this.notice(this.tran('Please input danmaku content!'));
  484. return;
  485. }
  486. const danmakuData = {
  487. token: this.option.danmaku.token,
  488. player: this.option.danmaku.id,
  489. author: this.option.danmaku.user,
  490. time: this.video.currentTime,
  491. text: commentInput.value,
  492. color: this.element.querySelector('.dplayer-comment-setting-color input:checked').value,
  493. type: this.element.querySelector('.dplayer-comment-setting-type input:checked').value
  494. };
  495. this.option.apiBackend.send(this.option.danmaku.api, danmakuData);
  496. commentInput.value = '';
  497. closeComment();
  498. this.dan.splice(this.danIndex, 0, danmakuData);
  499. this.danIndex++;
  500. const danmaku = {
  501. text: htmlEncode(danmakuData.text),
  502. color: danmakuData.color,
  503. type: danmakuData.type,
  504. border: `2px solid ${this.option.theme}`
  505. };
  506. this.pushDanmaku(danmaku);
  507. };
  508. const closeCommentSetting = () => {
  509. if (commentSettingBox.classList.contains('dplayer-comment-setting-open')) {
  510. commentSettingBox.classList.remove('dplayer-comment-setting-open');
  511. }
  512. };
  513. const toggleCommentSetting = () => {
  514. if (commentSettingBox.classList.contains('dplayer-comment-setting-open')) {
  515. commentSettingBox.classList.remove('dplayer-comment-setting-open');
  516. }
  517. else {
  518. commentSettingBox.classList.add('dplayer-comment-setting-open');
  519. }
  520. };
  521. let disableHide = 0;
  522. let commentFocusTimeout = 0;
  523. const closeComment = () => {
  524. if (!commentBox.classList.contains('dplayer-comment-box-open')) {
  525. return;
  526. }
  527. commentBox.classList.remove('dplayer-comment-box-open');
  528. mask.classList.remove('dplayer-mask-show');
  529. this.element.classList.remove('dplayer-show-controller');
  530. clearInterval(disableHide);
  531. clearTimeout(commentFocusTimeout);
  532. closeCommentSetting();
  533. };
  534. const openComment = () => {
  535. if (commentBox.classList.contains('dplayer-comment-box-open')) {
  536. return;
  537. }
  538. commentBox.classList.add('dplayer-comment-box-open');
  539. mask.classList.add('dplayer-mask-show');
  540. this.element.classList.add('dplayer-show-controller');
  541. disableHide = setInterval(() => {
  542. clearTimeout(this.hideTime);
  543. }, 1000);
  544. commentFocusTimeout = setTimeout(() => {
  545. commentInput.focus();
  546. }, 300);
  547. };
  548. mask.addEventListener('click', () => {
  549. closeComment();
  550. });
  551. commentIcon.addEventListener('click', () => {
  552. openComment();
  553. });
  554. commentSettingIcon.addEventListener('click', () => {
  555. toggleCommentSetting();
  556. });
  557. // comment setting box
  558. this.element.getElementsByClassName('dplayer-comment-setting-color')[0].addEventListener('click', () => {
  559. const sele = this.element.querySelector('input[name="dplayer-danmaku-color-${index}"]:checked+span');
  560. if (sele) {
  561. commentSettingIcon.getElementsByClassName('dplayer-fill')[0].style.fill = this.element.querySelector('input[name="dplayer-danmaku-color-${index}"]:checked').value;
  562. }
  563. });
  564. commentInput.addEventListener('click', () => {
  565. closeCommentSetting();
  566. });
  567. commentInput.addEventListener('keydown', (e) => {
  568. const event = e || window.event;
  569. if (event.keyCode === 13) {
  570. sendComment();
  571. }
  572. });
  573. commentSendIcon.addEventListener('click', sendComment);
  574. /**
  575. * full screen
  576. */
  577. const resetAnimation = () => {
  578. const danWidth = danContainer.offsetWidth;
  579. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  580. for (let i = 0; i < items.length; i++) {
  581. items[i].style.transform = `translateX(-${danWidth}px)`;
  582. }
  583. };
  584. this.element.addEventListener('fullscreenchange', () => {
  585. resetAnimation();
  586. });
  587. this.element.addEventListener('mozfullscreenchange', () => {
  588. resetAnimation();
  589. });
  590. this.element.addEventListener('webkitfullscreenchange', () => {
  591. resetAnimation();
  592. });
  593. // browser full screen
  594. this.element.getElementsByClassName('dplayer-full-icon')[0].addEventListener('click', () => {
  595. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
  596. if (this.element.requestFullscreen) {
  597. this.element.requestFullscreen();
  598. }
  599. else if (this.element.mozRequestFullScreen) {
  600. this.element.mozRequestFullScreen();
  601. }
  602. else if (this.element.webkitRequestFullscreen) {
  603. this.element.webkitRequestFullscreen();
  604. }
  605. else if (this.video.webkitEnterFullscreen) { // Safari for iOS
  606. this.video.webkitEnterFullscreen();
  607. }
  608. }
  609. else {
  610. if (document.cancelFullScreen) {
  611. document.cancelFullScreen();
  612. }
  613. else if (document.mozCancelFullScreen) {
  614. document.mozCancelFullScreen();
  615. }
  616. else if (document.webkitCancelFullScreen) {
  617. document.webkitCancelFullScreen();
  618. }
  619. }
  620. resetAnimation();
  621. });
  622. // web full screen
  623. this.element.getElementsByClassName('dplayer-full-in-icon')[0].addEventListener('click', () => {
  624. if (this.element.classList.contains('dplayer-fulled')) {
  625. this.element.classList.remove('dplayer-fulled');
  626. }
  627. else {
  628. this.element.classList.add('dplayer-fulled');
  629. resetAnimation();
  630. }
  631. });
  632. /**
  633. * hot key
  634. */
  635. const handleKeyDown = (e) => {
  636. const tag = document.activeElement.tagName.toUpperCase();
  637. const editable = document.activeElement.getAttribute('contenteditable');
  638. if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') {
  639. const event = e || window.event;
  640. let percentage;
  641. switch (event.keyCode) {
  642. case 32:
  643. event.preventDefault();
  644. this.toggle();
  645. break;
  646. case 37:
  647. event.preventDefault();
  648. this.seek(this.video.currentTime - 5);
  649. break;
  650. case 39:
  651. event.preventDefault();
  652. this.seek(this.video.currentTime + 5);
  653. break;
  654. case 38:
  655. event.preventDefault();
  656. percentage = this.video.volume + 0.1;
  657. this.volume(percentage);
  658. break;
  659. case 40:
  660. event.preventDefault();
  661. percentage = this.video.volume - 0.1;
  662. this.volume(percentage);
  663. break;
  664. }
  665. }
  666. };
  667. if (this.option.hotkey) {
  668. document.addEventListener('keydown', handleKeyDown);
  669. }
  670. document.addEventListener('keydown', (e) => { // Press ESC to quit web full screen
  671. const event = e || window.event;
  672. switch (event.keyCode) {
  673. case 27:
  674. if (this.element.classList.contains('dplayer-fulled')) {
  675. this.element.classList.remove('dplayer-fulled');
  676. resetAnimation();
  677. }
  678. break;
  679. }
  680. });
  681. /**
  682. * right key
  683. */
  684. const menu = this.element.getElementsByClassName('dplayer-menu')[0];
  685. this.element.addEventListener('contextmenu', (e) => {
  686. const event = e || window.event;
  687. event.preventDefault();
  688. menu.classList.add('dplayer-menu-show');
  689. const clientRect = this.element.getBoundingClientRect();
  690. const menuLeft = event.clientX - clientRect.left;
  691. const menuTop = event.clientY - clientRect.top;
  692. if (menuLeft + menu.offsetWidth >= clientRect.width) {
  693. menu.style.right = clientRect.width - menuLeft + 'px';
  694. menu.style.left = 'initial';
  695. }
  696. else {
  697. menu.style.left = event.clientX - this.element.getBoundingClientRect().left + 'px';
  698. menu.style.right = 'initial';
  699. }
  700. if (menuTop + menu.offsetHeight >= clientRect.height) {
  701. menu.style.bottom = clientRect.height - menuTop + 'px';
  702. menu.style.top = 'initial';
  703. }
  704. else {
  705. menu.style.top = event.clientY - this.element.getBoundingClientRect().top + 'px';
  706. menu.style.bottom = 'initial';
  707. }
  708. mask.classList.add('dplayer-mask-show');
  709. mask.addEventListener('click', () => {
  710. mask.classList.remove('dplayer-mask-show');
  711. menu.classList.remove('dplayer-menu-show');
  712. });
  713. });
  714. /**
  715. * Switch quality
  716. */
  717. if (this.option.video.quality) {
  718. this.element.getElementsByClassName('dplayer-quality-list')[0].addEventListener('click', (e) => {
  719. if (e.target.classList.contains('dplayer-quality-item')) {
  720. this.switchQuality(e.target.dataset.index);
  721. }
  722. });
  723. }
  724. /**
  725. * Screenshot
  726. */
  727. if (this.option.screenshot) {
  728. const camareIcon = this.element.getElementsByClassName('dplayer-camera-icon')[0];
  729. camareIcon.addEventListener('click', () => {
  730. const canvas = document.createElement("canvas");
  731. canvas.width = this.video.videoWidth;
  732. canvas.height = this.video.videoHeight;
  733. canvas.getContext('2d').drawImage(this.video, 0, 0, canvas.width, canvas.height);
  734. camareIcon.href = canvas.toDataURL();
  735. camareIcon.download = "DPlayer.png";
  736. });
  737. }
  738. index++;
  739. }
  740. /**
  741. * Seek video
  742. */
  743. seek (time) {
  744. time = Math.max(time, 0);
  745. if (this.video.duration) {
  746. time = Math.min(time, this.video.duration);
  747. }
  748. this.video.currentTime = time;
  749. for (let i = 0; i < this.dan.length; i++) {
  750. if (this.dan[i].time >= time) {
  751. this.danIndex = i;
  752. return;
  753. }
  754. this.danIndex = this.dan.length;
  755. }
  756. }
  757. /**
  758. * Play video
  759. */
  760. play () {
  761. this.paused = false;
  762. if (this.video.paused) {
  763. this.bezel.innerHTML = svg('play');
  764. this.bezel.classList.add('dplayer-bezel-transition');
  765. }
  766. this.playButton.innerHTML = svg('pause');
  767. this.video.play();
  768. this.setTime();
  769. this.element.classList.add('dplayer-playing');
  770. this.trigger('play');
  771. }
  772. /**
  773. * Pause video
  774. */
  775. pause () {
  776. this.paused = true;
  777. this.element.classList.remove('dplayer-loading');
  778. if (!this.video.paused) {
  779. this.bezel.innerHTML = svg('pause');
  780. this.bezel.classList.add('dplayer-bezel-transition');
  781. }
  782. this.ended = false;
  783. this.playButton.innerHTML = svg('play');
  784. this.video.pause();
  785. this.clearTime();
  786. this.element.classList.remove('dplayer-playing');
  787. window.cancelAnimationFrame(this.requestID);
  788. this.trigger('pause');
  789. }
  790. /**
  791. * Set volume
  792. */
  793. volume (percentage) {
  794. percentage = percentage > 0 ? percentage : 0;
  795. percentage = percentage < 1 ? percentage : 1;
  796. this.updateBar('volume', percentage, 'width');
  797. this.video.volume = percentage;
  798. if (this.video.muted) {
  799. this.video.muted = false;
  800. }
  801. this.switchVolumeIcon();
  802. }
  803. /**
  804. * Toggle between play and pause
  805. */
  806. toggle () {
  807. if (this.video.paused) {
  808. this.play();
  809. }
  810. else {
  811. this.pause();
  812. }
  813. }
  814. /**
  815. * attach event
  816. */
  817. on (event, callback) {
  818. if (typeof callback === 'function') {
  819. this.event[event].push(callback);
  820. }
  821. }
  822. /**
  823. * Asynchronously read danmaku from all API endpoints
  824. */
  825. _readAllEndpoints (endpoints, finish) {
  826. const results = [];
  827. let readCount = 0;
  828. const cbk = (i) => (err, data) => {
  829. ++readCount;
  830. if (err) {
  831. if (err.response) {
  832. this.notice(err.response.msg);
  833. }
  834. else {
  835. this.notice('Request was unsuccessful: ' + err.status);
  836. }
  837. results[i] = [];
  838. }
  839. else {
  840. results[i] = data;
  841. }
  842. if (readCount === endpoints.length) {
  843. return finish(results);
  844. }
  845. };
  846. for (let i = 0; i < endpoints.length; ++i) {
  847. this.option.apiBackend.read(endpoints[i], cbk(i));
  848. }
  849. }
  850. /**
  851. * Read danmaku from API
  852. */
  853. readDanmaku () {
  854. let apiurl;
  855. if (this.option.danmaku.maximum) {
  856. apiurl = `${this.option.danmaku.api}?id=${this.option.danmaku.id}&max=${this.option.danmaku.maximum}`;
  857. }
  858. else {
  859. apiurl = `${this.option.danmaku.api}?id=${this.option.danmaku.id}`;
  860. }
  861. const endpoints = (this.option.danmaku.addition || []).slice(0);
  862. endpoints.push(apiurl);
  863. this._readAllEndpoints(endpoints, (results) => {
  864. this.danIndex = 0;
  865. this.dan = [].concat.apply([], results).sort((a, b) => a.time - b.time);
  866. this.element.getElementsByClassName('dplayer-danloading')[0].style.display = 'none';
  867. // autoplay
  868. if (this.option.autoplay && !isMobile) {
  869. this.play();
  870. }
  871. else if (isMobile) {
  872. this.pause();
  873. }
  874. });
  875. }
  876. /**
  877. * Push a danmaku into DPlayer
  878. *
  879. * @param {Object Array} danmaku - {text, color, type}
  880. * text - danmaku content
  881. * color - danmaku color, default: `#fff`
  882. * type - danmaku type, `right` `top` `bottom`, default: `right`
  883. */
  884. pushDanmaku (danmaku) {
  885. const danContainer = this.element.getElementsByClassName('dplayer-danmaku')[0];
  886. const itemHeight = this.arrow ? 24 : 30;
  887. const danWidth = danContainer.offsetWidth;
  888. const danHeight = danContainer.offsetHeight;
  889. const itemY = parseInt(danHeight / itemHeight);
  890. const danItemRight = (ele) => {
  891. const eleWidth = ele.offsetWidth || parseInt(ele.style.width);
  892. const eleRight = ele.getBoundingClientRect().right || danContainer.getBoundingClientRect().right + eleWidth;
  893. return danContainer.getBoundingClientRect().right - eleRight;
  894. };
  895. const danSpeed = (width) => (danWidth + width) / 5;
  896. const getTunnel = (ele, type, width) => {
  897. const tmp = danWidth / danSpeed(width);
  898. for (let i = 0; ; i++) {
  899. const item = this.danTunnel[type][i + ''];
  900. if (item && item.length) {
  901. for (let j = 0; j < item.length; j++) {
  902. const danRight = danItemRight(item[j]) - 10;
  903. if (danRight <= danWidth - tmp * danSpeed(parseInt(item[j].style.width)) || danRight <= 0) {
  904. break;
  905. }
  906. if (j === item.length - 1) {
  907. this.danTunnel[type][i + ''].push(ele);
  908. ele.addEventListener('animationend', () => {
  909. this.danTunnel[type][i + ''].splice(0, 1);
  910. });
  911. return i % itemY;
  912. }
  913. }
  914. }
  915. else {
  916. this.danTunnel[type][i + ''] = [ele];
  917. ele.addEventListener('animationend', () => {
  918. this.danTunnel[type][i + ''].splice(0, 1);
  919. });
  920. return i % itemY;
  921. }
  922. }
  923. };
  924. if (Object.prototype.toString.call(danmaku) !== '[object Array]') {
  925. danmaku = [danmaku];
  926. }
  927. const docFragment = document.createDocumentFragment();
  928. for (let i = 0; i < danmaku.length; i++) {
  929. if (!danmaku[i].type) {
  930. danmaku[i].type = 'right';
  931. }
  932. if (!danmaku[i].color) {
  933. danmaku[i].color = '#fff';
  934. }
  935. const item = document.createElement(`div`);
  936. item.classList.add(`dplayer-danmaku-item`);
  937. item.classList.add(`dplayer-danmaku-${danmaku[i].type}`);
  938. item.innerHTML = danmaku[i].text;
  939. item.style.opacity = this.danOpacity;
  940. item.style.color = danmaku[i].color;
  941. item.style.border = danmaku[i].border;
  942. item.addEventListener('animationend', () => {
  943. danContainer.removeChild(item);
  944. });
  945. const itemWidth = this.danmakuMeasure(danmaku[i].text);
  946. // adjust
  947. switch (danmaku[i].type) {
  948. case 'right':
  949. item.style.width = itemWidth + 1 + 'px';
  950. item.style.top = itemHeight * getTunnel(item, danmaku[i].type, itemWidth) + 'px';
  951. item.style.transform = `translateX(-${danWidth}px)`;
  952. break;
  953. case 'top':
  954. item.style.top = itemHeight * getTunnel(item, danmaku[i].type) + 'px';
  955. break;
  956. case 'bottom':
  957. item.style.bottom = itemHeight * getTunnel(item, danmaku[i].type) + 'px';
  958. break;
  959. default:
  960. console.error(`Can't handled danmaku type: ${danmaku[i].type}`);
  961. }
  962. // move
  963. item.classList.add(`dplayer-danmaku-move`);
  964. // insert
  965. docFragment.appendChild(item);
  966. }
  967. danContainer.appendChild(docFragment);
  968. return docFragment;
  969. }
  970. /**
  971. * Switch to a new video
  972. *
  973. * @param {Object} video - new video info
  974. * @param {Object} danmaku - new danmaku info
  975. */
  976. switchVideo (video, danmaku) {
  977. this.video.poster = video.pic ? video.pic : '';
  978. this.video.src = video.url;
  979. this.pause();
  980. if (danmaku) {
  981. this.dan = [];
  982. this.danIndex = 0;
  983. this.element.getElementsByClassName('dplayer-danloading')[0].style.display = 'block';
  984. this.updateBar('played', 0, 'width');
  985. this.updateBar('loaded', 0, 'width');
  986. this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = '00:00';
  987. this.element.getElementsByClassName('dplayer-danmaku')[0].innerHTML = '';
  988. this.danTuel = {
  989. right: {},
  990. top: {},
  991. bottom: {}
  992. };
  993. this.option.danmaku = danmaku;
  994. this.readDanmaku();
  995. }
  996. }
  997. initVideo () {
  998. // Support HTTP Live Streaming
  999. let enablehls;
  1000. if (this.option.video.type === 'auto') {
  1001. enablehls = /m3u8(#|\?|$)/i.exec(this.video.src);
  1002. }
  1003. else if (this.option.video.type === 'hls') {
  1004. enablehls = true;
  1005. }
  1006. else {
  1007. enablehls = false;
  1008. }
  1009. if (enablehls && Hls.isSupported()) {
  1010. // this.element.getElementsByClassName('dplayer-time')[0].style.display = 'none';
  1011. const hls = new Hls();
  1012. hls.loadSource(this.video.src);
  1013. hls.attachMedia(this.video);
  1014. }
  1015. // Support FLV
  1016. let enableflv;
  1017. if (this.option.video.type === 'auto') {
  1018. enableflv = /.flv(#|\?|$)/i.exec(this.video.src);
  1019. }
  1020. else if (this.option.video.type === 'flv') {
  1021. enableflv = true;
  1022. }
  1023. else {
  1024. enableflv = false;
  1025. }
  1026. if (enableflv && flvjs.isSupported()) {
  1027. const flvPlayer = flvjs.createPlayer({
  1028. type: 'flv',
  1029. url: this.option.video.url
  1030. });
  1031. flvPlayer.attachMediaElement(this.video);
  1032. flvPlayer.load();
  1033. }
  1034. // if (this.option.danmaku) {
  1035. // this.video.addEventListener('seeking', () => {
  1036. // for (let i = 0; i < this.dan.length; i++) {
  1037. // if (this.dan[i].time >= this.video.currentTime) {
  1038. // this.danIndex = i;
  1039. // return;
  1040. // }
  1041. // this.danIndex = this.dan.length;
  1042. // }
  1043. // });
  1044. // }
  1045. /**
  1046. * video events
  1047. */
  1048. // show video time: the metadata has loaded or changed
  1049. this.video.addEventListener('durationchange', () => {
  1050. if (this.video.duration !== 1) { // compatibility: Android browsers will output 1 at first
  1051. this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = utils.secondToTime(this.video.duration);
  1052. }
  1053. });
  1054. // show video loaded bar: to inform interested parties of progress downloading the media
  1055. this.video.addEventListener('progress', () => {
  1056. const percentage = this.video.buffered.length ? this.video.buffered.end(this.video.buffered.length - 1) / this.video.duration : 0;
  1057. this.updateBar('loaded', percentage, 'width');
  1058. });
  1059. // video download error: an error occurs
  1060. this.video.addEventListener('error', () => {
  1061. this.tran && this.notice && this.notice(this.tran('This video fails to load'), -1);
  1062. this.trigger && this.trigger('pause');
  1063. });
  1064. // video can play: enough data is available that the media can be played
  1065. this.video.addEventListener('canplay', () => {
  1066. this.trigger('canplay');
  1067. });
  1068. // video end
  1069. this.ended = false;
  1070. this.video.addEventListener('ended', () => {
  1071. this.updateBar('played', 1, 'width');
  1072. if (!this.loop) {
  1073. this.ended = true;
  1074. this.pause();
  1075. this.trigger('ended');
  1076. }
  1077. else {
  1078. this.seek(0);
  1079. this.video.play();
  1080. }
  1081. this.danIndex = 0;
  1082. });
  1083. this.video.addEventListener('play', () => {
  1084. if (this.paused) {
  1085. this.play();
  1086. }
  1087. });
  1088. this.video.addEventListener('pause', () => {
  1089. if (!this.paused) {
  1090. this.pause();
  1091. }
  1092. });
  1093. // control volume
  1094. this.video.volume = parseInt(this.element.getElementsByClassName('dplayer-volume-bar-inner')[0].style.width) / 100;
  1095. }
  1096. switchQuality (index) {
  1097. if (this.qualityIndex === index || this.switchingQuality) {
  1098. return;
  1099. }
  1100. else {
  1101. this.qualityIndex = index;
  1102. }
  1103. this.switchingQuality = true;
  1104. this.quality = this.option.video.quality[index];
  1105. this.element.getElementsByClassName('dplayer-quality-icon')[0].innerHTML = this.quality.name;
  1106. const paused = this.video.paused;
  1107. this.video.pause();
  1108. const videoHTML = html.video(false, null, this.option.screenshot, 'auto', this.quality.url);
  1109. const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
  1110. const parent = this.element.getElementsByClassName('dplayer-video-wrap')[0];
  1111. parent.insertBefore(videoEle, parent.getElementsByTagName('div')[0]);
  1112. this.prevVideo = this.video;
  1113. this.video = videoEle;
  1114. this.initVideo();
  1115. this.seek(this.prevVideo.currentTime);
  1116. this.notice(`${this.tran('Switching to')} ${this.quality.name} ${this.tran('quality')}`, -1);
  1117. this.video.addEventListener('canplay', () => {
  1118. if (this.prevVideo) {
  1119. if (this.video.currentTime !== this.prevVideo.currentTime) {
  1120. this.seek(this.prevVideo.currentTime);
  1121. return;
  1122. }
  1123. parent.removeChild(this.prevVideo);
  1124. this.video.classList.add('dplayer-video-current');
  1125. if (!paused) {
  1126. this.video.play();
  1127. }
  1128. this.prevVideo = null;
  1129. this.notice(`${this.tran('Switched to')} ${this.quality.name} ${this.tran('quality')}`);
  1130. this.switchingQuality = false;
  1131. }
  1132. });
  1133. }
  1134. timeTipsHandler (pbar, timeTips) {
  1135. // http://stackoverflow.com/questions/1480133/how-can-i-get-an-objects-absolute-position-on-the-page-in-javascript
  1136. const cumulativeOffset = (element) => {
  1137. let top = 0, left = 0;
  1138. do {
  1139. top += element.offsetTop || 0;
  1140. left += element.offsetLeft || 0;
  1141. element = element.offsetParent;
  1142. } while (element);
  1143. return {
  1144. top: top,
  1145. left: left
  1146. };
  1147. };
  1148. return (e) => {
  1149. if (!this.video.duration) {
  1150. return;
  1151. }
  1152. const { clientX } = e;
  1153. const px = cumulativeOffset(pbar).left;
  1154. const tx = clientX - px;
  1155. timeTips.innerText = utils.secondToTime(this.video.duration * (tx / pbar.offsetWidth));
  1156. timeTips.style.left = `${(tx - 20)}px`;
  1157. switch (e.type) {
  1158. case 'mouseenter':
  1159. case 'mouseover':
  1160. case 'mousemove':
  1161. if (this.isTipsShow) {
  1162. return;
  1163. }
  1164. timeTips.classList.remove('hidden');
  1165. this.isTipsShow = true;
  1166. break;
  1167. case 'mouseleave':
  1168. case 'mouseout':
  1169. if (!this.isTipsShow) {
  1170. return;
  1171. }
  1172. timeTips.classList.add('hidden');
  1173. this.isTipsShow = false;
  1174. break;
  1175. }
  1176. };
  1177. }
  1178. notice (text, time) {
  1179. const noticeEle = this.element.getElementsByClassName('dplayer-notice')[0];
  1180. noticeEle.innerHTML = text;
  1181. noticeEle.style.opacity = 1;
  1182. if (this.noticeTime) {
  1183. clearTimeout(this.noticeTime);
  1184. }
  1185. if (time && time < 0) {
  1186. return;
  1187. }
  1188. this.noticeTime = setTimeout(() => {
  1189. noticeEle.style.opacity = 0;
  1190. }, time || 2000);
  1191. }
  1192. destroy () {
  1193. this.pause();
  1194. clearTimeout(this.hideTime);
  1195. this.video.src = '';
  1196. this.element.innerHTML = '';
  1197. for (const key in this) {
  1198. if (this.hasOwnProperty(key) && key !== 'paused') {
  1199. delete this[key];
  1200. }
  1201. }
  1202. }
  1203. }
  1204. module.exports = DPlayer;