player.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. import Promise from 'promise-polyfill';
  2. import utils from './utils';
  3. import handleOption from './options';
  4. import { i18n } from './i18n';
  5. import Template from './template';
  6. import Icons from './icons';
  7. import Danmaku from './danmaku';
  8. import Events from './events';
  9. import FullScreen from './fullscreen';
  10. import User from './user';
  11. import Subtitle from './subtitle';
  12. import Subtitles from './subtitles';
  13. import Bar from './bar';
  14. import Timer from './timer';
  15. import Bezel from './bezel';
  16. import Controller from './controller';
  17. import Setting from './setting';
  18. import Comment from './comment';
  19. import HotKey from './hotkey';
  20. import ContextMenu from './contextmenu';
  21. import InfoPanel from './info-panel';
  22. import tplVideo from '../template/video.art';
  23. let index = 0;
  24. const instances = [];
  25. class DPlayer {
  26. /**
  27. * DPlayer constructor function
  28. *
  29. * @param {Object} options - See README
  30. * @constructor
  31. */
  32. constructor(options) {
  33. this.options = handleOption({ preload: options.video.type === 'webtorrent' ? 'none' : 'metadata', ...options });
  34. if (this.options.video.quality) {
  35. this.qualityIndex = this.options.video.defaultQuality;
  36. this.quality = this.options.video.quality[this.options.video.defaultQuality];
  37. }
  38. this.tran = new i18n(this.options.lang).tran;
  39. this.events = new Events();
  40. this.user = new User(this);
  41. this.container = this.options.container;
  42. this.noticeList = {};
  43. this.container.classList.add('dplayer');
  44. if (!this.options.danmaku) {
  45. this.container.classList.add('dplayer-no-danmaku');
  46. }
  47. if (this.options.live) {
  48. this.container.classList.add('dplayer-live');
  49. } else {
  50. this.container.classList.remove('dplayer-live');
  51. }
  52. if (utils.isMobile) {
  53. this.container.classList.add('dplayer-mobile');
  54. }
  55. this.arrow = this.container.offsetWidth <= 500;
  56. if (this.arrow) {
  57. this.container.classList.add('dplayer-arrow');
  58. }
  59. // multi subtitles defaultSubtitle add index, off option
  60. if (this.options.subtitle) {
  61. if (Array.isArray(this.options.subtitle.url)) {
  62. const offSubtitle = {
  63. subtitle: '',
  64. lang: 'off',
  65. };
  66. this.options.subtitle.url.push(offSubtitle);
  67. if (this.options.subtitle.defaultSubtitle) {
  68. if (typeof this.options.subtitle.defaultSubtitle === 'string') {
  69. // defaultSubtitle is string, match in lang then name.
  70. this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) =>
  71. /* if (sub.lang === this.options.subtitle.defaultSubtitle) {
  72. return true;
  73. } else if (sub.name === this.options.subtitle.defaultSubtitle) {
  74. return true;
  75. } else {
  76. return false;
  77. } */
  78. sub.lang === this.options.subtitle.defaultSubtitle ? true : sub.name === this.options.subtitle.defaultSubtitle ? true : false
  79. );
  80. } else if (typeof this.options.subtitle.defaultSubtitle === 'number') {
  81. // defaultSubtitle is int, directly use for index
  82. this.options.subtitle.index = this.options.subtitle.defaultSubtitle;
  83. }
  84. }
  85. // defaultSubtitle not match or not exist or index bound(when defaultSubtitle is int), try browser language.
  86. if (this.options.subtitle.index === -1 || !this.options.subtitle.index || this.options.subtitle.index > this.options.subtitle.url.length - 1) {
  87. this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) => sub.lang === this.options.lang);
  88. }
  89. // browser language not match, default off title
  90. if (this.options.subtitle.index === -1) {
  91. this.options.subtitle.index = this.options.subtitle.url.length - 1;
  92. }
  93. }
  94. }
  95. this.template = new Template({
  96. container: this.container,
  97. options: this.options,
  98. index: index,
  99. tran: this.tran,
  100. });
  101. this.video = this.template.video;
  102. this.bar = new Bar(this.template);
  103. this.bezel = new Bezel(this.template.bezel);
  104. this.fullScreen = new FullScreen(this);
  105. this.controller = new Controller(this);
  106. if (this.options.danmaku) {
  107. this.danmaku = new Danmaku({
  108. player: this,
  109. container: this.template.danmaku,
  110. opacity: this.user.get('opacity'),
  111. callback: () => {
  112. setTimeout(() => {
  113. this.template.danmakuLoading.style.display = 'none';
  114. // autoplay
  115. if (this.options.autoplay) {
  116. this.play();
  117. }
  118. }, 0);
  119. },
  120. error: (msg) => {
  121. this.notice(msg);
  122. },
  123. apiBackend: this.options.apiBackend,
  124. borderColor: this.options.theme,
  125. height: this.arrow ? 24 : 30,
  126. time: () => this.video.currentTime,
  127. unlimited: this.user.get('unlimited'),
  128. api: {
  129. id: this.options.danmaku.id,
  130. address: this.options.danmaku.api,
  131. token: this.options.danmaku.token,
  132. maximum: this.options.danmaku.maximum,
  133. addition: this.options.danmaku.addition,
  134. user: this.options.danmaku.user,
  135. speedRate: this.options.danmaku.speedRate,
  136. },
  137. events: this.events,
  138. tran: (msg) => this.tran(msg),
  139. });
  140. this.comment = new Comment(this);
  141. }
  142. this.setting = new Setting(this);
  143. this.plugins = {};
  144. this.docClickFun = () => {
  145. this.focus = false;
  146. };
  147. this.containerClickFun = () => {
  148. this.focus = true;
  149. };
  150. document.addEventListener('click', this.docClickFun, true);
  151. this.container.addEventListener('click', this.containerClickFun, true);
  152. this.paused = true;
  153. this.timer = new Timer(this);
  154. this.hotkey = new HotKey(this);
  155. this.contextmenu = new ContextMenu(this);
  156. this.initVideo(this.video, (this.quality && this.quality.type) || this.options.video.type);
  157. this.infoPanel = new InfoPanel(this);
  158. if (!this.danmaku && this.options.autoplay) {
  159. this.play();
  160. }
  161. this.moveBar = false;
  162. index++;
  163. instances.push(this);
  164. }
  165. /**
  166. * Seek video
  167. */
  168. seek(time) {
  169. time = Math.max(time, 0);
  170. if (this.video.duration) {
  171. time = Math.min(time, this.video.duration);
  172. }
  173. if (this.video.currentTime < time) {
  174. this.notice(`${this.tran('ff').replace('%s', (time - this.video.currentTime).toFixed(0))}`);
  175. } else if (this.video.currentTime > time) {
  176. this.notice(`${this.tran('rew').replace('%s', (this.video.currentTime - time).toFixed(0))}`);
  177. }
  178. this.video.currentTime = time;
  179. if (this.danmaku) {
  180. this.danmaku.seek();
  181. }
  182. this.bar.set('played', time / this.video.duration, 'width');
  183. this.template.ptime.innerHTML = utils.secondToTime(time);
  184. }
  185. /**
  186. * Play video
  187. */
  188. play(fromNative) {
  189. this.paused = false;
  190. if (this.video.paused && !utils.isMobile) {
  191. this.bezel.switch(Icons.play);
  192. }
  193. this.template.playButton.innerHTML = Icons.pause;
  194. this.template.mobilePlayButton.innerHTML = Icons.pause;
  195. if (!fromNative) {
  196. const playedPromise = Promise.resolve(this.video.play());
  197. playedPromise
  198. .catch(() => {
  199. this.pause();
  200. })
  201. .then(() => {});
  202. }
  203. this.timer.enable('loading');
  204. this.container.classList.remove('dplayer-paused');
  205. this.container.classList.add('dplayer-playing');
  206. if (this.danmaku) {
  207. this.danmaku.play();
  208. }
  209. if (this.options.mutex) {
  210. for (let i = 0; i < instances.length; i++) {
  211. if (this !== instances[i]) {
  212. instances[i].pause();
  213. }
  214. }
  215. }
  216. }
  217. /**
  218. * Pause video
  219. */
  220. pause(fromNative) {
  221. this.paused = true;
  222. this.container.classList.remove('dplayer-loading');
  223. if (!this.video.paused && !utils.isMobile) {
  224. this.bezel.switch(Icons.pause);
  225. }
  226. this.template.playButton.innerHTML = Icons.play;
  227. this.template.mobilePlayButton.innerHTML = Icons.play;
  228. if (!fromNative) {
  229. this.video.pause();
  230. }
  231. this.timer.disable('loading');
  232. this.container.classList.remove('dplayer-playing');
  233. this.container.classList.add('dplayer-paused');
  234. if (this.danmaku) {
  235. this.danmaku.pause();
  236. }
  237. }
  238. switchVolumeIcon() {
  239. if (this.volume() >= 0.95) {
  240. this.template.volumeIcon.innerHTML = Icons.volumeUp;
  241. } else if (this.volume() > 0) {
  242. this.template.volumeIcon.innerHTML = Icons.volumeDown;
  243. } else {
  244. this.template.volumeIcon.innerHTML = Icons.volumeOff;
  245. }
  246. }
  247. /**
  248. * Set volume
  249. */
  250. volume(percentage, nostorage, nonotice) {
  251. percentage = parseFloat(percentage);
  252. if (!isNaN(percentage)) {
  253. percentage = Math.max(percentage, 0);
  254. percentage = Math.min(percentage, 1);
  255. this.bar.set('volume', percentage, 'width');
  256. const formatPercentage = `${(percentage * 100).toFixed(0)}%`;
  257. this.template.volumeBarWrapWrap.dataset.balloon = formatPercentage;
  258. if (!nostorage) {
  259. this.user.set('volume', percentage);
  260. }
  261. if (!nonotice) {
  262. this.notice(`${this.tran('volume')} ${(percentage * 100).toFixed(0)}%`, undefined, undefined, 'volume');
  263. }
  264. this.video.volume = percentage;
  265. if (this.video.muted) {
  266. this.video.muted = false;
  267. }
  268. this.switchVolumeIcon();
  269. }
  270. return this.video.volume;
  271. }
  272. /**
  273. * Toggle between play and pause
  274. */
  275. toggle() {
  276. if (this.video.paused) {
  277. this.play();
  278. } else {
  279. this.pause();
  280. }
  281. }
  282. /**
  283. * attach event
  284. */
  285. on(name, callback) {
  286. this.events.on(name, callback);
  287. }
  288. /**
  289. * Switch to a new video
  290. *
  291. * @param {Object} video - new video info
  292. * @param {Object} danmaku - new danmaku info
  293. */
  294. switchVideo(video, danmakuAPI) {
  295. this.pause();
  296. this.video.poster = video.pic ? video.pic : '';
  297. this.video.src = video.url;
  298. this.initMSE(this.video, video.type || 'auto');
  299. if (danmakuAPI) {
  300. this.template.danmakuLoading.style.display = 'block';
  301. this.bar.set('played', 0, 'width');
  302. this.bar.set('loaded', 0, 'width');
  303. this.template.ptime.innerHTML = '00:00';
  304. this.template.danmaku.innerHTML = '';
  305. if (this.danmaku) {
  306. this.danmaku.reload({
  307. id: danmakuAPI.id,
  308. address: danmakuAPI.api,
  309. token: danmakuAPI.token,
  310. maximum: danmakuAPI.maximum,
  311. addition: danmakuAPI.addition,
  312. user: danmakuAPI.user,
  313. });
  314. }
  315. }
  316. }
  317. initMSE(video, type) {
  318. this.type = type;
  319. if (this.options.video.customType && this.options.video.customType[type]) {
  320. if (Object.prototype.toString.call(this.options.video.customType[type]) === '[object Function]') {
  321. this.options.video.customType[type](this.video, this);
  322. } else {
  323. console.error(`Illegal customType: ${type}`);
  324. }
  325. } else {
  326. if (this.type === 'auto') {
  327. if (/m3u8(#|\?|$)/i.exec(video.src)) {
  328. this.type = 'hls';
  329. } else if (/.flv(#|\?|$)/i.exec(video.src)) {
  330. this.type = 'flv';
  331. } else if (/.mpd(#|\?|$)/i.exec(video.src)) {
  332. this.type = 'dash';
  333. } else {
  334. this.type = 'normal';
  335. }
  336. }
  337. if (this.type === 'hls' && (video.canPlayType('application/x-mpegURL') || video.canPlayType('application/vnd.apple.mpegURL'))) {
  338. this.type = 'normal';
  339. }
  340. switch (this.type) {
  341. // https://github.com/video-dev/hls.js
  342. case 'hls':
  343. if (window.Hls) {
  344. if (window.Hls.isSupported()) {
  345. const options = this.options.pluginOptions.hls;
  346. const hls = new window.Hls(options);
  347. this.plugins.hls = hls;
  348. hls.loadSource(video.src);
  349. hls.attachMedia(video);
  350. this.events.on('destroy', () => {
  351. hls.destroy();
  352. delete this.plugins.hls;
  353. });
  354. } else {
  355. this.notice('Error: Hls is not supported.');
  356. }
  357. } else {
  358. this.notice("Error: Can't find Hls.");
  359. }
  360. break;
  361. // https://github.com/Bilibili/flv.js
  362. case 'flv':
  363. if (window.flvjs) {
  364. if (window.flvjs.isSupported()) {
  365. const flvPlayer = window.flvjs.createPlayer(
  366. Object.assign(this.options.pluginOptions.flv.mediaDataSource || {}, {
  367. type: 'flv',
  368. url: video.src,
  369. }),
  370. this.options.pluginOptions.flv.config
  371. );
  372. this.plugins.flvjs = flvPlayer;
  373. flvPlayer.attachMediaElement(video);
  374. flvPlayer.load();
  375. this.events.on('destroy', () => {
  376. flvPlayer.unload();
  377. flvPlayer.detachMediaElement();
  378. flvPlayer.destroy();
  379. delete this.plugins.flvjs;
  380. });
  381. } else {
  382. this.notice('Error: flvjs is not supported.');
  383. }
  384. } else {
  385. this.notice("Error: Can't find flvjs.");
  386. }
  387. break;
  388. // https://github.com/Dash-Industry-Forum/dash.js
  389. case 'dash':
  390. if (window.dashjs) {
  391. const dashjsPlayer = window.dashjs.MediaPlayer().create();
  392. dashjsPlayer.initialize(video, video.src, false, 0);
  393. const options = this.options.pluginOptions.dash;
  394. dashjsPlayer.updateSettings(options);
  395. this.plugins.dash = dashjsPlayer;
  396. this.events.on('destroy', () => {
  397. window.dashjs.MediaPlayer().reset();
  398. delete this.plugins.dash;
  399. });
  400. } else {
  401. this.notice("Error: Can't find dashjs.");
  402. }
  403. break;
  404. // https://github.com/webtorrent/webtorrent
  405. case 'webtorrent':
  406. if (window.WebTorrent) {
  407. if (window.WebTorrent.WEBRTC_SUPPORT) {
  408. this.container.classList.add('dplayer-loading');
  409. const options = this.options.pluginOptions.webtorrent;
  410. const client = new window.WebTorrent(options);
  411. this.plugins.webtorrent = client;
  412. const torrentId = video.src;
  413. video.src = '';
  414. video.preload = 'metadata';
  415. video.addEventListener('durationchange', () => this.container.classList.remove('dplayer-loading'), { once: true });
  416. client.add(torrentId, (torrent) => {
  417. const file = torrent.files.find((file) => file.name.endsWith('.mp4'));
  418. file.renderTo(this.video, {
  419. autoplay: this.options.autoplay,
  420. controls: false,
  421. });
  422. });
  423. this.events.on('destroy', () => {
  424. client.remove(torrentId);
  425. client.destroy();
  426. delete this.plugins.webtorrent;
  427. });
  428. } else {
  429. this.notice('Error: Webtorrent is not supported.');
  430. }
  431. } else {
  432. this.notice("Error: Can't find Webtorrent.");
  433. }
  434. break;
  435. }
  436. }
  437. }
  438. initVideo(video, type) {
  439. this.initMSE(video, type);
  440. /**
  441. * video events
  442. */
  443. // show video time: the metadata has loaded or changed
  444. this.on('durationchange', () => {
  445. // compatibility: Android browsers will output 1 or Infinity at first
  446. if (video.duration !== 1 && video.duration !== Infinity) {
  447. this.template.dtime.innerHTML = utils.secondToTime(video.duration);
  448. }
  449. });
  450. // show video loaded bar: to inform interested parties of progress downloading the media
  451. this.on('progress', () => {
  452. const percentage = video.buffered.length ? video.buffered.end(video.buffered.length - 1) / video.duration : 0;
  453. this.bar.set('loaded', percentage, 'width');
  454. });
  455. // video download error: an error occurs
  456. this.on('error', () => {
  457. if (!this.video.error) {
  458. // Not a video load error, may be poster load failed, see #307
  459. return;
  460. }
  461. this.tran && this.notice && this.type !== 'webtorrent' && this.notice(this.tran('video-failed'));
  462. });
  463. // video end
  464. this.on('ended', () => {
  465. this.bar.set('played', 1, 'width');
  466. if (!this.setting.loop) {
  467. this.pause();
  468. } else {
  469. this.seek(0);
  470. this.play();
  471. }
  472. if (this.danmaku) {
  473. this.danmaku.danIndex = 0;
  474. }
  475. });
  476. this.on('play', () => {
  477. if (this.paused) {
  478. this.play(true);
  479. }
  480. });
  481. this.on('pause', () => {
  482. if (!this.paused) {
  483. this.pause(true);
  484. }
  485. });
  486. this.on('timeupdate', () => {
  487. if (!this.moveBar) {
  488. this.bar.set('played', this.video.currentTime / this.video.duration, 'width');
  489. }
  490. const currentTime = utils.secondToTime(this.video.currentTime);
  491. if (this.template.ptime.innerHTML !== currentTime) {
  492. this.template.ptime.innerHTML = currentTime;
  493. }
  494. });
  495. for (let i = 0; i < this.events.videoEvents.length; i++) {
  496. video.addEventListener(this.events.videoEvents[i], (e) => {
  497. this.events.trigger(this.events.videoEvents[i], e);
  498. });
  499. }
  500. this.volume(this.user.get('volume'), true, true);
  501. if (this.options.subtitle) {
  502. // init old single subtitle function(sub show and style)
  503. this.subtitle = new Subtitle(this.template.subtitle, this.video, this.options.subtitle, this.events);
  504. // init multi subtitles function(sub update)
  505. if (Array.isArray(this.options.subtitle.url)) {
  506. this.subtitles = new Subtitles(this);
  507. }
  508. if (!this.user.get('subtitle')) {
  509. this.subtitle.hide();
  510. }
  511. }
  512. }
  513. switchQuality(index) {
  514. index = typeof index === 'string' ? parseInt(index) : index;
  515. if (this.qualityIndex === index || this.switchingQuality) {
  516. return;
  517. } else {
  518. this.prevIndex = this.qualityIndex;
  519. this.qualityIndex = index;
  520. }
  521. this.switchingQuality = true;
  522. this.quality = this.options.video.quality[index];
  523. this.template.qualityButton.innerHTML = this.quality.name;
  524. const paused = this.video.paused;
  525. this.video.pause();
  526. const videoHTML = tplVideo({
  527. current: false,
  528. pic: null,
  529. screenshot: this.options.screenshot,
  530. preload: 'auto',
  531. url: this.quality.url,
  532. subtitle: this.options.subtitle,
  533. });
  534. const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
  535. this.template.videoWrap.insertBefore(videoEle, this.template.videoWrap.getElementsByTagName('div')[0]);
  536. this.prevVideo = this.video;
  537. this.video = videoEle;
  538. this.initVideo(this.video, this.quality.type || this.options.video.type);
  539. this.seek(this.prevVideo.currentTime);
  540. this.notice(`${this.tran('switching-quality').replace('%q', this.quality.name)}`, -1, undefined, 'switch-quality');
  541. this.events.trigger('quality_start', this.quality);
  542. this.on('canplay', () => {
  543. if (this.prevVideo) {
  544. if (this.video.currentTime !== this.prevVideo.currentTime) {
  545. this.seek(this.prevVideo.currentTime);
  546. return;
  547. }
  548. this.template.videoWrap.removeChild(this.prevVideo);
  549. this.video.classList.add('dplayer-video-current');
  550. if (!paused) {
  551. this.video.play();
  552. }
  553. this.prevVideo = null;
  554. this.notice(`${this.tran('switched-quality').replace('%q', this.quality.name)}`, undefined, undefined, 'switch-quality');
  555. this.switchingQuality = false;
  556. this.events.trigger('quality_end');
  557. }
  558. });
  559. this.on('error', () => {
  560. if (!this.video.error) {
  561. return;
  562. }
  563. if (this.prevVideo) {
  564. this.template.videoWrap.removeChild(this.video);
  565. this.video = this.prevVideo;
  566. if (!paused) {
  567. this.video.play();
  568. }
  569. this.qualityIndex = this.prevIndex;
  570. this.quality = this.options.video.quality[this.qualityIndex];
  571. this.noticeTime = setTimeout(() => {
  572. this.template.notice.style.opacity = 0;
  573. this.events.trigger('notice_hide');
  574. }, 3000);
  575. this.prevVideo = null;
  576. this.switchingQuality = false;
  577. }
  578. });
  579. }
  580. notice(text, time = 2000, opacity = 0.8, id) {
  581. let oldNoticeEle;
  582. if (id) {
  583. oldNoticeEle = document.getElementById(`dplayer-notice-${id}`);
  584. if (oldNoticeEle) {
  585. oldNoticeEle.innerHTML = text;
  586. }
  587. if (this.noticeList[id]) {
  588. clearTimeout(this.noticeList[id]);
  589. this.noticeList[id] = null;
  590. }
  591. }
  592. if (!oldNoticeEle) {
  593. const notice = Template.NewNotice(text, opacity, id);
  594. this.template.noticeList.appendChild(notice);
  595. oldNoticeEle = notice;
  596. }
  597. this.events.trigger('notice_show', oldNoticeEle);
  598. if (time > 0) {
  599. this.noticeList[id] = setTimeout(
  600. (function (e, dp) {
  601. return () => {
  602. e.addEventListener('animationend', () => {
  603. dp.template.noticeList.removeChild(e);
  604. });
  605. e.classList.add('remove-notice');
  606. dp.events.trigger('notice_hide');
  607. dp.noticeList[id] = null;
  608. };
  609. })(oldNoticeEle, this),
  610. time
  611. );
  612. }
  613. }
  614. resize() {
  615. if (this.danmaku) {
  616. this.danmaku.resize();
  617. }
  618. if (this.controller.thumbnails) {
  619. this.controller.thumbnails.resize(160, (this.video.videoHeight / this.video.videoWidth) * 160, this.template.barWrap.offsetWidth);
  620. }
  621. this.events.trigger('resize');
  622. }
  623. speed(rate) {
  624. this.video.playbackRate = rate;
  625. }
  626. destroy() {
  627. instances.splice(instances.indexOf(this), 1);
  628. this.pause();
  629. document.removeEventListener('click', this.docClickFun, true);
  630. this.container.removeEventListener('click', this.containerClickFun, true);
  631. this.fullScreen.destroy();
  632. this.hotkey.destroy();
  633. this.contextmenu.destroy();
  634. this.controller.destroy();
  635. this.timer.destroy();
  636. this.video.src = '';
  637. this.container.innerHTML = '';
  638. this.events.trigger('destroy');
  639. }
  640. static get version() {
  641. /* global DPLAYER_VERSION */
  642. return DPLAYER_VERSION;
  643. }
  644. }
  645. export default DPlayer;