DPlayer.js 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. (() => {
  2. class DPlayer {
  3. /**
  4. * DPlayer constructor function
  5. *
  6. * @param {Object} option - See README
  7. * @constructor
  8. */
  9. constructor(option) {
  10. this.svg = {
  11. 'play': ['0 0 16 32', 'M15.552 15.168q0.448 0.32 0.448 0.832 0 0.448-0.448 0.768l-13.696 8.512q-0.768 0.512-1.312 0.192t-0.544-1.28v-16.448q0-0.96 0.544-1.28t1.312 0.192z'],
  12. 'pause': ['0 0 17 32', 'M14.080 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048zM2.88 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048z'],
  13. 'volume-up': ['0 0 21 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528zM25.152 16q0 2.72-1.536 5.056t-4 3.36q-0.256 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.704 0.672-1.056 1.024-0.512 1.376-0.8 1.312-0.96 2.048-2.4t0.736-3.104-0.736-3.104-2.048-2.4q-0.352-0.288-1.376-0.8-0.672-0.352-0.672-1.056 0-0.448 0.32-0.8t0.8-0.352q0.224 0 0.48 0.096 2.496 1.056 4 3.36t1.536 5.056zM29.728 16q0 4.096-2.272 7.552t-6.048 5.056q-0.224 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.64 0.704-1.056 0.128-0.064 0.384-0.192t0.416-0.192q0.8-0.448 1.44-0.896 2.208-1.632 3.456-4.064t1.216-5.152-1.216-5.152-3.456-4.064q-0.64-0.448-1.44-0.896-0.128-0.096-0.416-0.192t-0.384-0.192q-0.704-0.416-0.704-1.056 0-0.448 0.32-0.8t0.832-0.352q0.224 0 0.448 0.096 3.776 1.632 6.048 5.056t2.272 7.552z'],
  14. 'volume-down': ['0 0 21 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528z'],
  15. 'volume-off': ['0 0 21 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8z'],
  16. 'loop': ['0 0 32 32', 'M1.882 16.941c0 4.152 3.221 7.529 7.177 7.529v1.882c-4.996 0-9.060-4.222-9.060-9.412s4.064-9.412 9.060-9.412h7.96l-3.098-3.098 1.331-1.331 5.372 5.37-5.37 5.372-1.333-1.333 3.1-3.098h-7.962c-3.957 0-7.177 3.377-7.177 7.529zM22.94 7.529v1.882c3.957 0 7.177 3.377 7.177 7.529s-3.221 7.529-7.177 7.529h-7.962l3.098-3.098-1.331-1.331-5.37 5.37 5.372 5.372 1.331-1.331-3.1-3.1h7.96c4.998 0 9.062-4.222 9.062-9.412s-4.064-9.412-9.060-9.412z'],
  17. 'full': ['0 0 32 33', 'M6.667 28h-5.333c-0.8 0-1.333-0.533-1.333-1.333v-5.333c0-0.8 0.533-1.333 1.333-1.333s1.333 0.533 1.333 1.333v4h4c0.8 0 1.333 0.533 1.333 1.333s-0.533 1.333-1.333 1.333zM30.667 28h-5.333c-0.8 0-1.333-0.533-1.333-1.333s0.533-1.333 1.333-1.333h4v-4c0-0.8 0.533-1.333 1.333-1.333s1.333 0.533 1.333 1.333v5.333c0 0.8-0.533 1.333-1.333 1.333zM30.667 12c-0.8 0-1.333-0.533-1.333-1.333v-4h-4c-0.8 0-1.333-0.533-1.333-1.333s0.533-1.333 1.333-1.333h5.333c0.8 0 1.333 0.533 1.333 1.333v5.333c0 0.8-0.533 1.333-1.333 1.333zM1.333 12c-0.8 0-1.333-0.533-1.333-1.333v-5.333c0-0.8 0.533-1.333 1.333-1.333h5.333c0.8 0 1.333 0.533 1.333 1.333s-0.533 1.333-1.333 1.333h-4v4c0 0.8-0.533 1.333-1.333 1.333z'],
  18. 'setting': ['0 0 32 28', 'M28.633 17.104c0.035 0.21 0.026 0.463-0.026 0.76s-0.14 0.598-0.262 0.904c-0.122 0.306-0.271 0.581-0.445 0.825s-0.367 0.419-0.576 0.524c-0.209 0.105-0.393 0.157-0.55 0.157s-0.332-0.035-0.524-0.105c-0.175-0.052-0.393-0.1-0.655-0.144s-0.528-0.052-0.799-0.026c-0.271 0.026-0.541 0.083-0.812 0.17s-0.502 0.236-0.694 0.445c-0.419 0.437-0.664 0.934-0.734 1.493s0.009 1.092 0.236 1.598c0.175 0.349 0.148 0.699-0.079 1.048-0.105 0.14-0.271 0.284-0.498 0.432s-0.476 0.284-0.747 0.406-0.555 0.218-0.851 0.288c-0.297 0.070-0.559 0.105-0.786 0.105-0.157 0-0.306-0.061-0.445-0.183s-0.236-0.253-0.288-0.393h-0.026c-0.192-0.541-0.52-1.009-0.982-1.402s-1-0.589-1.611-0.589c-0.594 0-1.131 0.197-1.611 0.589s-0.816 0.851-1.009 1.375c-0.087 0.21-0.218 0.362-0.393 0.458s-0.367 0.144-0.576 0.144c-0.244 0-0.52-0.044-0.825-0.131s-0.611-0.197-0.917-0.327c-0.306-0.131-0.581-0.284-0.825-0.458s-0.428-0.349-0.55-0.524c-0.087-0.122-0.135-0.266-0.144-0.432s0.057-0.397 0.197-0.694c0.192-0.402 0.266-0.86 0.223-1.375s-0.266-0.991-0.668-1.428c-0.244-0.262-0.541-0.432-0.891-0.511s-0.681-0.109-0.995-0.092c-0.367 0.017-0.742 0.087-1.127 0.21-0.244 0.070-0.489 0.052-0.734-0.052-0.192-0.070-0.371-0.231-0.537-0.485s-0.314-0.533-0.445-0.838c-0.131-0.306-0.231-0.62-0.301-0.943s-0.087-0.59-0.052-0.799c0.052-0.384 0.227-0.629 0.524-0.734 0.524-0.21 0.995-0.555 1.415-1.035s0.629-1.017 0.629-1.611c0-0.611-0.21-1.144-0.629-1.598s-0.891-0.786-1.415-0.996c-0.157-0.052-0.288-0.179-0.393-0.38s-0.157-0.406-0.157-0.616c0-0.227 0.035-0.48 0.105-0.76s0.162-0.55 0.275-0.812 0.244-0.502 0.393-0.72c0.148-0.218 0.31-0.38 0.485-0.485 0.14-0.087 0.275-0.122 0.406-0.105s0.275 0.052 0.432 0.105c0.524 0.21 1.070 0.275 1.637 0.197s1.070-0.327 1.506-0.747c0.21-0.209 0.362-0.467 0.458-0.773s0.157-0.607 0.183-0.904c0.026-0.297 0.026-0.568 0-0.812s-0.048-0.419-0.065-0.524c-0.035-0.105-0.066-0.227-0.092-0.367s-0.013-0.262 0.039-0.367c0.105-0.244 0.293-0.458 0.563-0.642s0.563-0.336 0.878-0.458c0.314-0.122 0.62-0.214 0.917-0.275s0.533-0.092 0.707-0.092c0.227 0 0.406 0.074 0.537 0.223s0.223 0.301 0.275 0.458c0.192 0.471 0.507 0.886 0.943 1.244s0.952 0.537 1.546 0.537c0.611 0 1.153-0.17 1.624-0.511s0.803-0.773 0.996-1.297c0.070-0.14 0.179-0.284 0.327-0.432s0.301-0.223 0.458-0.223c0.244 0 0.511 0.035 0.799 0.105s0.572 0.166 0.851 0.288c0.279 0.122 0.537 0.279 0.773 0.472s0.423 0.402 0.563 0.629c0.087 0.14 0.113 0.293 0.079 0.458s-0.070 0.284-0.105 0.354c-0.227 0.506-0.297 1.039-0.21 1.598s0.341 1.048 0.76 1.467c0.419 0.419 0.934 0.651 1.546 0.694s1.179-0.057 1.703-0.301c0.14-0.087 0.31-0.122 0.511-0.105s0.371 0.096 0.511 0.236c0.262 0.244 0.493 0.616 0.694 1.113s0.336 1 0.406 1.506c0.035 0.297-0.013 0.528-0.144 0.694s-0.266 0.275-0.406 0.327c-0.542 0.192-1.004 0.528-1.388 1.009s-0.576 1.026-0.576 1.637c0 0.594 0.162 1.113 0.485 1.559s0.747 0.764 1.27 0.956c0.122 0.070 0.227 0.14 0.314 0.21 0.192 0.157 0.323 0.358 0.393 0.602v0zM16.451 19.462c0.786 0 1.528-0.149 2.227-0.445s1.305-0.707 1.821-1.231c0.515-0.524 0.921-1.131 1.218-1.821s0.445-1.428 0.445-2.214c0-0.786-0.148-1.524-0.445-2.214s-0.703-1.292-1.218-1.808c-0.515-0.515-1.122-0.921-1.821-1.218s-1.441-0.445-2.227-0.445c-0.786 0-1.524 0.148-2.214 0.445s-1.292 0.703-1.808 1.218c-0.515 0.515-0.921 1.118-1.218 1.808s-0.445 1.428-0.445 2.214c0 0.786 0.149 1.524 0.445 2.214s0.703 1.297 1.218 1.821c0.515 0.524 1.118 0.934 1.808 1.231s1.428 0.445 2.214 0.445v0z'],
  19. 'right': ['0 0 32 32', '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'],
  20. 'comment': ['0 0 32 32', 'M27.128 0.38h-22.553c-2.336 0-4.229 1.825-4.229 4.076v16.273c0 2.251 1.893 4.076 4.229 4.076h4.229v-2.685h8.403l-8.784 8.072 1.566 1.44 7.429-6.827h9.71c2.335 0 4.229-1.825 4.229-4.076v-16.273c0-2.252-1.894-4.076-4.229-4.076zM28.538 19.403c0 1.5-1.262 2.717-2.819 2.717h-8.36l-0.076-0.070-0.076 0.070h-11.223c-1.557 0-2.819-1.217-2.819-2.717v-13.589c0-1.501 1.262-2.718 2.819-2.718h19.734c1.557 0 2.819-0.141 2.819 1.359v14.947zM9.206 10.557c-1.222 0-2.215 0.911-2.215 2.036s0.992 2.035 2.215 2.035c1.224 0 2.216-0.911 2.216-2.035s-0.992-2.036-2.216-2.036zM22.496 10.557c-1.224 0-2.215 0.911-2.215 2.036s0.991 2.035 2.215 2.035c1.224 0 2.215-0.911 2.215-2.035s-0.991-2.036-2.215-2.036zM15.852 10.557c-1.224 0-2.215 0.911-2.215 2.036s0.991 2.035 2.215 2.035c1.222 0 2.215-0.911 2.215-2.035s-0.992-2.036-2.215-2.036z'],
  21. 'comment-off': ['0 0 32 32', 'M27.090 0.131h-22.731c-2.354 0-4.262 1.839-4.262 4.109v16.401c0 2.269 1.908 4.109 4.262 4.109h4.262v-2.706h8.469l-8.853 8.135 1.579 1.451 7.487-6.88h9.787c2.353 0 4.262-1.84 4.262-4.109v-16.401c0-2.27-1.909-4.109-4.262-4.109v0zM28.511 19.304c0 1.512-1.272 2.738-2.841 2.738h-8.425l-0.076-0.070-0.076 0.070h-11.311c-1.569 0-2.841-1.226-2.841-2.738v-13.696c0-1.513 1.272-2.739 2.841-2.739h19.889c1.569 0 2.841-0.142 2.841 1.37v15.064z'],
  22. 'send': ['0 0 32 32', 'M13.725 30l3.9-5.325-3.9-1.125v6.45zM0 17.5l11.050 3.35 13.6-11.55-10.55 12.425 11.8 3.65 6.1-23.375-32 15.5z']
  23. };
  24. this.getSVG = (type) => {
  25. return `
  26. <svg xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" version="1.1" viewBox="${this.svg[type][0]}" width="100%">
  27. <use class="dplayer-svg-shadow" xlink:href="#dplayer-${type}"></use>
  28. <path class="dplayer-fill" d="${this.svg[type][1]}" id="dplayer-${type}"></path>
  29. </svg>
  30. `;
  31. };
  32. this.isMobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i);
  33. // compatibility: some mobile browsers don't suppose autoplay
  34. if (this.isMobile) {
  35. option.autoplay = false;
  36. }
  37. // default options
  38. const defaultOption = {
  39. element: document.getElementsByClassName('dplayer')[0],
  40. autoplay: false,
  41. theme: '#b7daff',
  42. loop: false
  43. };
  44. for (let defaultKey in defaultOption) {
  45. if (defaultOption.hasOwnProperty(defaultKey) && !option.hasOwnProperty(defaultKey)) {
  46. option[defaultKey] = defaultOption[defaultKey];
  47. }
  48. }
  49. this.option = option;
  50. this.loop = option.loop;
  51. /**
  52. * Parse second to 00:00 format
  53. *
  54. * @param {Number} second
  55. * @return {String} 00:00 format
  56. */
  57. this.secondToTime = (second) => {
  58. const add0 = (num) => {
  59. return num < 10 ? '0' + num : '' + num;
  60. };
  61. const min = parseInt(second / 60);
  62. const sec = parseInt(second - min * 60);
  63. return add0(min) + ':' + add0(sec);
  64. };
  65. /**
  66. * Update progress bar, including loading progress bar and play progress bar
  67. *
  68. * @param {String} type - Point out which bar it is, should be played loaded or volume
  69. * @param {Number} percentage
  70. * @param {String} direction - Point out the direction of this bar, Should be height or width
  71. */
  72. this.updateBar = (type, percentage, direction) => {
  73. percentage = percentage > 0 ? percentage : 0;
  74. percentage = percentage < 1 ? percentage : 1;
  75. this[type + 'Bar'].style[direction] = percentage * 100 + '%';
  76. };
  77. // define DPlayer events
  78. this.eventTypes = ['play', 'pause', 'canplay', 'playing', 'ended', 'error'];
  79. this.event = {};
  80. for (let i = 0; i < this.eventTypes.length; i++) {
  81. this.event[this.eventTypes[i]] = [];
  82. }
  83. this.trigger = (type) => {
  84. for (let i = 0; i < this.event[type].length; i++) {
  85. this.event[type][i]();
  86. }
  87. }
  88. }
  89. /**
  90. * AutoLink initialization function
  91. */
  92. init() {
  93. this.element = this.option.element;
  94. if (!this.option.danmaku) {
  95. this.element.classList.add('dplayer-no-danmaku');
  96. }
  97. this.element.innerHTML = `
  98. <div class="dplayer-mask"></div>
  99. <div class="dplayer-video-wrap">
  100. <video class="dplayer-video" ${this.option.video.pic ? `poster="${this.option.video.pic}"` : ``}>
  101. <source src="${this.option.video.url}" type="video/mp4">
  102. </video>
  103. <div class="dplayer-danmaku"></div>
  104. <div class="dplayer-bezel">
  105. <span class="dplayer-bezel-icon"></span>
  106. <span class="diplayer-loading-icon">
  107. <svg height="100%" version="1.1" viewBox="0 0 22 22" width="100%">
  108. <svg x="7" y="1">
  109. <circle class="diplayer-loading-dot diplayer-loading-dot-0" cx="4" cy="4" r="2"></circle>
  110. </svg>
  111. <svg x="11" y="3">
  112. <circle class="diplayer-loading-dot diplayer-loading-dot-1" cx="4" cy="4" r="2"></circle>
  113. </svg>
  114. <svg x="13" y="7">
  115. <circle class="diplayer-loading-dot diplayer-loading-dot-2" cx="4" cy="4" r="2"></circle>
  116. </svg>
  117. <svg x="11" y="11">
  118. <circle class="diplayer-loading-dot diplayer-loading-dot-3" cx="4" cy="4" r="2"></circle>
  119. </svg>
  120. <svg x="7" y="13">
  121. <circle class="diplayer-loading-dot diplayer-loading-dot-4" cx="4" cy="4" r="2"></circle>
  122. </svg>
  123. <svg x="3" y="11">
  124. <circle class="diplayer-loading-dot diplayer-loading-dot-5" cx="4" cy="4" r="2"></circle>
  125. </svg>
  126. <svg x="1" y="7">
  127. <circle class="diplayer-loading-dot diplayer-loading-dot-6" cx="4" cy="4" r="2"></circle>
  128. </svg>
  129. <svg x="3" y="3">
  130. <circle class="diplayer-loading-dot diplayer-loading-dot-7" cx="4" cy="4" r="2"></circle>
  131. </svg>
  132. </svg>
  133. </span>
  134. </div>
  135. </div>
  136. <div class="dplayer-controller-mask"></div>
  137. <div class="dplayer-controller">
  138. <div class="dplayer-icons dplayer-icons-left">
  139. <button class="dplayer-icon dplayer-play-icon">`
  140. + this.getSVG('play')
  141. + ` </button>
  142. <div class="dplayer-volume">
  143. <button class="dplayer-icon dplayer-volume-icon">`
  144. + this.getSVG('volume-down')
  145. + ` </button>
  146. <div class="dplayer-volume-bar-wrap">
  147. <div class="dplayer-volume-bar">
  148. <div class="dplayer-volume-bar-inner" style="width: 70%; background: ${this.option.theme};">
  149. <span class="dplayer-thumb" style="background: ${this.option.theme}"></span>
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. <span class="dplayer-time"><span class="dplayer-ptime">0:00</span> / <span class="dplayer-dtime">0:00</span></span>
  155. </div>
  156. <div class="dplayer-icons dplayer-icons-right">
  157. <div class="dplayer-comment">
  158. <button class="dplayer-icon dplayer-comment-icon">`
  159. + this.getSVG('comment')
  160. + ` </button>
  161. <div class="dplayer-comment-box">
  162. <div class="dplayer-comment-setting"></div>
  163. <div class="dplayer-comment-setting-box">
  164. <div class="dplayer-comment-setting-type">
  165. <label>
  166. <input type="radio" name="dplayer-danmaku-type" value="right" checked>
  167. <span>正常</span>
  168. </label>
  169. <label>
  170. <input type="radio" name="dplayer-danmaku-type" value="top">
  171. <span>顶部</span>
  172. </label>
  173. <label>
  174. <input type="radio" name="dplayer-danmaku-type" value="bottom">
  175. <span>底部</span>
  176. </label>
  177. </div>
  178. <div class="dplayer-comment-setting-color">
  179. <label>
  180. <input type="radio" name="dplayer-danmaku-color" value="#fff" checked>
  181. <span style="background: #fff; border: 1px solid rgba(0,0,0,.1);"></span>
  182. </label>
  183. <label>
  184. <input type="radio" name="dplayer-danmaku-color" value="#e54256">
  185. <span style="background: #e54256"></span>
  186. </label>
  187. <label>
  188. <input type="radio" name="dplayer-danmaku-color" value="#ffe133">
  189. <span style="background: #ffe133"></span>
  190. </label>
  191. <label>
  192. <input type="radio" name="dplayer-danmaku-color" value="#39ccff">
  193. <span style="background: #39ccff"></span>
  194. </label>
  195. <label>
  196. <input type="radio" name="dplayer-danmaku-color" value="#f424ff">
  197. <span style="background: #f424ff"></span>
  198. </label>
  199. <label>
  200. <input type="radio" name="dplayer-danmaku-color" value="#ff9d33">
  201. <span style="background: #ff9d33"></span>
  202. </label>
  203. <label>
  204. <input type="radio" name="dplayer-danmaku-color" value="#bde846">
  205. <span style="background: #bde846"></span>
  206. </label>
  207. <label>
  208. <input type="radio" name="dplayer-danmaku-color" value="#444">
  209. <span style="background: #444;"></span>
  210. </label>
  211. </div>
  212. </div>
  213. <input class="dplayer-comment-input" type="text" placeholder="输入弹幕,回车发送" maxlength="30">
  214. <button class="dplayer-icon dplayer-send-icon">`
  215. + this.getSVG('send')
  216. + ` </button>
  217. </div>
  218. </div>
  219. <div class="dplayer-setting">
  220. <button class="dplayer-icon dplayer-setting-icon">`
  221. + this.getSVG('setting')
  222. + ` </button>
  223. <div class="dplayer-setting-box"></div>
  224. </div>
  225. <button class="dplayer-icon dplayer-full-icon">`
  226. + this.getSVG('full')
  227. + ` </button>
  228. </div>
  229. <div class="dplayer-bar-wrap">
  230. <div class="dplayer-bar">
  231. <div class="dplayer-loaded" style="width: 0;"></div>
  232. <div class="dplayer-played" style="width: 0; background: ${this.option.theme}">
  233. <span class="dplayer-thumb" style="background: ${this.option.theme}"></span>
  234. </div>
  235. </div>
  236. </div>
  237. </div>
  238. <div class="dplayer-menu">
  239. <div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="http://diygod.me/">关于作者</a></span></div>
  240. <div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="https://github.com/DIYgod/DPlayer">关于 DPlayer 播放器</a></span></div>
  241. </div>
  242. `;
  243. // get this audio object
  244. this.audio = this.element.getElementsByClassName('dplayer-video')[0];
  245. this.bezel = this.element.getElementsByClassName('dplayer-bezel-icon')[0];
  246. this.bezel.addEventListener('animationend', () => {
  247. this.bezel.classList.remove('dplayer-bezel-transition');
  248. });
  249. this.ptime = this.element.getElementsByClassName('dplayer-ptime')[0];
  250. // play and pause button
  251. this.playButton = this.element.getElementsByClassName('dplayer-play-icon')[0];
  252. this.shouldpause = true;
  253. this.toggle = () => {
  254. if (this.audio.paused) {
  255. this.play();
  256. }
  257. else {
  258. this.pause();
  259. }
  260. };
  261. this.playButton.addEventListener('click', this.toggle);
  262. this.element.getElementsByClassName('dplayer-video-wrap')[0].addEventListener('click', this.toggle);
  263. this.element.getElementsByClassName('dplayer-controller-mask')[0].addEventListener('click', this.toggle);
  264. /**
  265. * control play progress
  266. */
  267. // get element's view position
  268. const getElementViewLeft = (element) => {
  269. let actualLeft = element.offsetLeft;
  270. let current = element.offsetParent;
  271. let elementScrollLeft;
  272. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
  273. while (current !== null) {
  274. actualLeft += current.offsetLeft;
  275. current = current.offsetParent;
  276. }
  277. }
  278. else {
  279. while (current !== null && current !== this.element) {
  280. actualLeft += current.offsetLeft;
  281. current = current.offsetParent;
  282. }
  283. }
  284. elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;
  285. return actualLeft - elementScrollLeft;
  286. };
  287. const getElementViewTop = (element) => {
  288. let actualTop = element.offsetTop;
  289. let current = element.offsetParent;
  290. let elementScrollTop;
  291. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
  292. while (current !== null) {
  293. actualTop += current.offsetTop;
  294. current = current.offsetParent;
  295. }
  296. }
  297. else {
  298. while (current !== null && current !== this.element) {
  299. actualTop += current.offsetTop;
  300. current = current.offsetParent;
  301. }
  302. }
  303. elementScrollTop = document.body.scrollTop + document.documentElement.scrollTop;
  304. return actualTop - elementScrollTop;
  305. };
  306. this.playedBar = this.element.getElementsByClassName('dplayer-played')[0];
  307. this.loadedBar = this.element.getElementsByClassName('dplayer-loaded')[0];
  308. this.bar = this.element.getElementsByClassName('dplayer-bar-wrap')[0];
  309. let barWidth;
  310. if (this.option.danmaku) {
  311. this.audio.addEventListener('seeked', () => {
  312. for (let i = 0; i < this.dan.length; i++) {
  313. if (this.dan[i].time >= this.audio.currentTime) {
  314. this.danIndex = i;
  315. return;
  316. }
  317. this.danIndex = this.dan.length;
  318. }
  319. });
  320. }
  321. let lastPlayPos = 0;
  322. let currentPlayPos = 0;
  323. let bufferingDetected = false;
  324. this.setTime = () => {
  325. if (this.option.danmaku) {
  326. this.playedTime = setInterval(() => {
  327. // whether the video is buffering
  328. currentPlayPos = this.audio.currentTime;
  329. if (!bufferingDetected
  330. && currentPlayPos < (lastPlayPos + 0.01)
  331. && !this.audio.paused) {
  332. this.element.classList.add('dplayer-loading');
  333. bufferingDetected = true;
  334. }
  335. if (bufferingDetected
  336. && currentPlayPos > (lastPlayPos + 0.01)
  337. && !this.audio.paused) {
  338. this.element.classList.remove('dplayer-loading');
  339. bufferingDetected = false;
  340. }
  341. lastPlayPos = currentPlayPos;
  342. this.updateBar('played', this.audio.currentTime / this.audio.duration, 'width');
  343. this.ptime.innerHTML = this.secondToTime(this.audio.currentTime);
  344. this.trigger('playing');
  345. const item = this.dan[this.danIndex];
  346. if (item && this.audio.currentTime >= parseFloat(item.time)) {
  347. this.danmakuIn(item.text, item.color, item.type);
  348. this.danIndex++;
  349. }
  350. }, 100);
  351. }
  352. else {
  353. this.playedTime = setInterval(() => {
  354. // whether the video is buffering
  355. currentPlayPos = this.audio.currentTime;
  356. if (!bufferingDetected
  357. && currentPlayPos < (lastPlayPos + 0.01)
  358. && !this.audio.paused) {
  359. this.element.classList.add('dplayer-loading');
  360. bufferingDetected = true
  361. }
  362. if (bufferingDetected
  363. && currentPlayPos > (lastPlayPos + 0.01)
  364. && !this.audio.paused) {
  365. this.element.classList.remove('dplayer-loading');
  366. bufferingDetected = false
  367. }
  368. lastPlayPos = currentPlayPos;
  369. this.updateBar('played', this.audio.currentTime / this.audio.duration, 'width');
  370. this.ptime.innerHTML = this.secondToTime(this.audio.currentTime);
  371. this.trigger('playing');
  372. }, 100);
  373. }
  374. };
  375. this.bar.addEventListener('click', (event) => {
  376. const e = event || window.event;
  377. barWidth = this.bar.clientWidth;
  378. let percentage = (e.clientX - getElementViewLeft(this.bar)) / barWidth;
  379. percentage = percentage > 0 ? percentage : 0;
  380. percentage = percentage < 1 ? percentage : 1;
  381. this.updateBar('played', percentage, 'width');
  382. this.audio.currentTime = parseFloat(this.playedBar.style.width) / 100 * this.audio.duration;
  383. });
  384. const thumbMove = (event) => {
  385. const e = event || window.event;
  386. let percentage = (e.clientX - getElementViewLeft(this.bar)) / barWidth;
  387. percentage = percentage > 0 ? percentage : 0;
  388. percentage = percentage < 1 ? percentage : 1;
  389. this.updateBar('played', percentage, 'width');
  390. this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = this.secondToTime(percentage * this.audio.duration);
  391. };
  392. const thumbUp = () => {
  393. document.removeEventListener('mouseup', thumbUp);
  394. document.removeEventListener('mousemove', thumbMove);
  395. this.audio.currentTime = parseFloat(this.playedBar.style.width) / 100 * this.audio.duration;
  396. this.setTime();
  397. };
  398. this.bar.addEventListener('mousedown', () => {
  399. barWidth = this.bar.clientWidth;
  400. clearInterval(this.playedTime);
  401. document.addEventListener('mousemove', thumbMove);
  402. document.addEventListener('mouseup', thumbUp);
  403. });
  404. /**
  405. * control volume
  406. */
  407. this.volumeBar = this.element.getElementsByClassName('dplayer-volume-bar-inner')[0];
  408. const volumeEle = this.element.getElementsByClassName('dplayer-volume')[0];
  409. const volumeBarWrapWrap = this.element.getElementsByClassName('dplayer-volume-bar-wrap')[0];
  410. const volumeBarWrap = this.element.getElementsByClassName('dplayer-volume-bar')[0];
  411. const volumeicon = this.element.getElementsByClassName('dplayer-volume-icon')[0];
  412. const vWidth = 35;
  413. const switchVolumeIcon = () => {
  414. if (this.audio.volume >= 0.8) {
  415. volumeicon.innerHTML = this.getSVG('volume-up');
  416. }
  417. else if (this.audio.volume > 0) {
  418. volumeicon.innerHTML = this.getSVG('volume-down');
  419. }
  420. else {
  421. volumeicon.innerHTML = this.getSVG('volume-off');
  422. }
  423. };
  424. const volumeMove = (event) => {
  425. const e = event || window.event;
  426. let percentage = (e.clientX - getElementViewLeft(volumeBarWrap) - 5.5) / vWidth;
  427. percentage = percentage > 0 ? percentage : 0;
  428. percentage = percentage < 1 ? percentage : 1;
  429. this.updateBar('volume', percentage, 'width');
  430. this.audio.volume = percentage;
  431. if (this.audio.muted) {
  432. this.audio.muted = false;
  433. }
  434. switchVolumeIcon();
  435. };
  436. const volumeUp = () => {
  437. document.removeEventListener('mouseup', volumeUp);
  438. document.removeEventListener('mousemove', volumeMove);
  439. volumeEle.classList.remove('dplayer-volume-active');
  440. };
  441. volumeBarWrapWrap.addEventListener('click', (event) => {
  442. const e = event || window.event;
  443. let percentage = (e.clientX - getElementViewLeft(volumeBarWrap) - 5.5) / vWidth;
  444. percentage = percentage > 0 ? percentage : 0;
  445. percentage = percentage < 1 ? percentage : 1;
  446. this.updateBar('volume', percentage, 'width');
  447. this.audio.volume = percentage;
  448. if (this.audio.muted) {
  449. this.audio.muted = false;
  450. }
  451. switchVolumeIcon();
  452. });
  453. volumeBarWrapWrap.addEventListener('mousedown', () => {
  454. document.addEventListener('mousemove', volumeMove);
  455. document.addEventListener('mouseup', volumeUp);
  456. volumeEle.classList.add('dplayer-volume-active');
  457. });
  458. volumeicon.addEventListener('click', () => {
  459. if (this.audio.muted) {
  460. this.audio.muted = false;
  461. switchVolumeIcon();
  462. this.updateBar('volume', this.audio.volume, 'width');
  463. }
  464. else {
  465. this.audio.muted = true;
  466. volumeicon.innerHTML = this.getSVG('volume-off');
  467. this.updateBar('volume', 0, 'width');
  468. }
  469. });
  470. /**
  471. * auto hide controller
  472. */
  473. let hideTime = 0;
  474. const hideController = () => {
  475. this.element.classList.remove('dplayer-hide-controller');
  476. clearTimeout(hideTime);
  477. hideTime = setTimeout(() => {
  478. if (this.audio.played.length) {
  479. this.element.classList.add('dplayer-hide-controller');
  480. closeSetting();
  481. closeComment();
  482. }
  483. }, 2000);
  484. };
  485. this.element.addEventListener('mousemove', hideController);
  486. this.element.addEventListener('click', hideController);
  487. /***
  488. * setting
  489. */
  490. this.danOpacity = 0.7;
  491. const settingHTML = {
  492. 'original': `
  493. <div class="dplayer-setting-item dplayer-setting-loop">
  494. <span class="dplayer-label">洗脑循环</span>
  495. <div class="dplayer-toggle">
  496. <input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle" id="dplayer-toggle">
  497. <label for="dplayer-toggle"></label>
  498. </div>
  499. </div>
  500. <div class="dplayer-setting-item dplayer-setting-speed">
  501. <span class="dplayer-label">速度</span>
  502. <div class="dplayer-toggle">`
  503. + this.getSVG('right')
  504. + ` </div>
  505. </div>
  506. <div class="dplayer-setting-item dplayer-setting-danmaku">
  507. <span class="dplayer-label">弹幕透明度</span>
  508. <div class="dplayer-danmaku-bar-wrap">
  509. <div class="dplayer-danmaku-bar">
  510. <div class="dplayer-danmaku-bar-inner" style="width: ${this.danOpacity * 100}%">
  511. <span class="dplayer-thumb"></span>
  512. </div>
  513. </div>
  514. </div>
  515. </div>`,
  516. 'speed': `
  517. <div class="dplayer-setting-speed-item" data-speed="0.5">
  518. <span class="dplayer-label">0.5</span>
  519. </div>
  520. <div class="dplayer-setting-speed-item" data-speed="0.75">
  521. <span class="dplayer-label">0.75</span>
  522. </div>
  523. <div class="dplayer-setting-speed-item" data-speed="1">
  524. <span class="dplayer-label">正常</span>
  525. </div>
  526. <div class="dplayer-setting-speed-item" data-speed="1.25">
  527. <span class="dplayer-label">1.25</span>
  528. </div>
  529. <div class="dplayer-setting-speed-item" data-speed="1.5">
  530. <span class="dplayer-label">1.5</span>
  531. </div>
  532. <div class="dplayer-setting-speed-item" data-speed="2">
  533. <span class="dplayer-label">2</span>
  534. </div>`
  535. };
  536. // toggle setting box
  537. const settingIcon = this.element.getElementsByClassName('dplayer-setting-icon')[0];
  538. const settingBox = this.element.getElementsByClassName('dplayer-setting-box')[0];
  539. const mask = this.element.getElementsByClassName('dplayer-mask')[0];
  540. settingBox.innerHTML = settingHTML.original;
  541. const closeSetting = () => {
  542. if (settingBox.classList.contains('dplayer-setting-box-open')) {
  543. settingBox.classList.remove('dplayer-setting-box-open');
  544. mask.classList.remove('dplayer-mask-show');
  545. setTimeout(() => {
  546. settingBox.classList.remove('dplayer-setting-box-narrow');
  547. settingBox.innerHTML = settingHTML.original;
  548. settingEvent();
  549. }, 300);
  550. }
  551. };
  552. const openSetting = () => {
  553. settingBox.classList.add('dplayer-setting-box-open');
  554. mask.classList.add('dplayer-mask-show');
  555. };
  556. mask.addEventListener('click', () => {
  557. closeSetting();
  558. });
  559. settingIcon.addEventListener('click', () => {
  560. openSetting();
  561. });
  562. const settingEvent = () => {
  563. // loop control
  564. const loopEle = this.element.getElementsByClassName('dplayer-setting-loop')[0];
  565. const loopToggle = loopEle.getElementsByClassName('dplayer-toggle-setting-input')[0];
  566. loopToggle.checked = this.loop;
  567. loopEle.addEventListener('click', () => {
  568. loopToggle.checked = !loopToggle.checked;
  569. if (loopToggle.checked) {
  570. this.loop = true;
  571. this.audio.loop = this.loop;
  572. }
  573. else {
  574. this.loop = false;
  575. this.audio.loop = this.loop;
  576. }
  577. closeSetting();
  578. });
  579. loopToggle.addEventListener('change', () => {
  580. if (loopToggle.checked) {
  581. this.loop = true;
  582. this.audio.loop = this.loop;
  583. }
  584. else {
  585. this.loop = false;
  586. this.audio.loop = this.loop;
  587. }
  588. closeSetting();
  589. });
  590. // speed control
  591. const speedEle = this.element.getElementsByClassName('dplayer-setting-speed')[0];
  592. speedEle.addEventListener('click', () => {
  593. settingBox.classList.add('dplayer-setting-box-narrow');
  594. settingBox.innerHTML = settingHTML.speed;
  595. const speedItem = settingBox.getElementsByClassName('dplayer-setting-speed-item');
  596. for (let i = 0; i < speedItem.length; i++) {
  597. speedItem[i].addEventListener('click', () => {
  598. this.audio.playbackRate = speedItem[i].dataset.speed;
  599. closeSetting();
  600. });
  601. }
  602. });
  603. if (this.option.danmaku) {
  604. // danmaku opacity
  605. this.danmakuBar = this.element.getElementsByClassName('dplayer-danmaku-bar-inner')[0];
  606. const danmakuBarWrapWrap = this.element.getElementsByClassName('dplayer-danmaku-bar-wrap')[0];
  607. const danmakuBarWrap = this.element.getElementsByClassName('dplayer-danmaku-bar')[0];
  608. const danmakuSettingBox = this.element.getElementsByClassName('dplayer-setting-danmaku')[0];
  609. const dWidth = 130;
  610. this.updateBar('danmaku', this.danOpacity, 'width');
  611. const danmakuMove = (event) => {
  612. const e = event || window.event;
  613. let percentage = (e.clientX - getElementViewLeft(danmakuBarWrap)) / dWidth;
  614. percentage = percentage > 0 ? percentage : 0;
  615. percentage = percentage < 1 ? percentage : 1;
  616. this.updateBar('danmaku', percentage, 'width');
  617. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  618. for (let i = 0; i < items.length; i++) {
  619. items[i].style.opacity = percentage;
  620. }
  621. this.danOpacity = percentage;
  622. };
  623. const danmakuUp = () => {
  624. document.removeEventListener('mouseup', danmakuUp);
  625. document.removeEventListener('mousemove', danmakuMove);
  626. danmakuSettingBox.classList.remove('dplayer-setting-danmaku-active');
  627. };
  628. danmakuBarWrapWrap.addEventListener('click', (event) => {
  629. const e = event || window.event;
  630. let percentage = (e.clientX - getElementViewLeft(danmakuBarWrap)) / dWidth;
  631. percentage = percentage > 0 ? percentage : 0;
  632. percentage = percentage < 1 ? percentage : 1;
  633. this.updateBar('danmaku', percentage, 'width');
  634. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  635. for (let i = 0; i < items.length; i++) {
  636. items[i].style.opacity = percentage;
  637. }
  638. this.danOpacity = percentage;
  639. });
  640. danmakuBarWrapWrap.addEventListener('mousedown', () => {
  641. document.addEventListener('mousemove', danmakuMove);
  642. document.addEventListener('mouseup', danmakuUp);
  643. danmakuSettingBox.classList.add('dplayer-setting-danmaku-active');
  644. });
  645. }
  646. };
  647. settingEvent();
  648. /**
  649. * audio events
  650. */
  651. // show audio time: the metadata has loaded or changed
  652. this.audio.addEventListener('durationchange', () => {
  653. if (this.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first
  654. this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = this.secondToTime(this.audio.duration);
  655. }
  656. });
  657. // show audio loaded bar: to inform interested parties of progress downloading the media
  658. this.audio.addEventListener('progress', () => {
  659. const percentage = this.audio.buffered.length ? this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration : 0;
  660. this.updateBar('loaded', percentage, 'width');
  661. });
  662. // audio download error: an error occurs
  663. this.audio.addEventListener('error', () => {
  664. this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = `Error happens ╥﹏╥`;
  665. this.trigger('pause');
  666. });
  667. // audio can play: enough data is available that the media can be played
  668. this.audio.addEventListener('canplay', () => {
  669. this.trigger('canplay');
  670. });
  671. // music end
  672. this.ended = false;
  673. this.audio.addEventListener('ended', () => {
  674. this.updateBar('played', 1, 'width');
  675. if (!this.loop) {
  676. this.ended = true;
  677. this.pause();
  678. this.trigger('ended');
  679. }
  680. });
  681. // control volume
  682. this.audio.volume = parseInt(this.element.getElementsByClassName('dplayer-volume-bar-inner')[0].style.width) / 100;
  683. // loop
  684. this.audio.loop = this.loop;
  685. // set duration time
  686. if (this.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first
  687. this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = this.audio.duration ? this.secondToTime(this.audio.duration) : '00:00';
  688. }
  689. /**
  690. * danmaku display
  691. */
  692. const danContainer = this.element.getElementsByClassName('dplayer-danmaku')[0];
  693. const itemHeight = 30;
  694. let danWidth;
  695. let danHeight;
  696. let itemY;
  697. let danTunnel = {
  698. right: {},
  699. top: {},
  700. bottom: {}
  701. };
  702. const danItemRight = (ele) => {
  703. return danContainer.getBoundingClientRect().right - ele.getBoundingClientRect().right;
  704. };
  705. const danSpeed = (ele) => {
  706. return (danWidth + ele.offsetWidth) / 5;
  707. };
  708. const getTunnel = (ele, type) => {
  709. const tmp = danWidth / danSpeed(ele);
  710. for (let i = 0; ; i++) {
  711. let item = danTunnel[type][i + ''];
  712. if (item && item.length) {
  713. for (let j = 0; j < item.length; j++) {
  714. const danRight = danItemRight(item[j]) - 10;
  715. if (danRight <= 640 - (tmp * danSpeed(item[j])) || danRight <= 0) {
  716. break;
  717. }
  718. if (j === item.length - 1) {
  719. danTunnel[type][i + ''].push(ele);
  720. ele.addEventListener('animationend', () => {
  721. danTunnel[type][i + ''].splice(0, 1);
  722. });
  723. return i % itemY;
  724. }
  725. }
  726. }
  727. else {
  728. danTunnel[type][i + ''] = [ele];
  729. ele.addEventListener('animationend', () => {
  730. danTunnel[type][i + ''].splice(0, 1);
  731. });
  732. return i % itemY;
  733. }
  734. }
  735. };
  736. this.danmakuIn = (text, color, type) => {
  737. danWidth = danContainer.offsetWidth;
  738. danHeight = danContainer.offsetHeight;
  739. itemY = danHeight / itemHeight;
  740. let item = document.createElement(`div`);
  741. let content = document.createTextNode(text);
  742. item.classList.add(`dplayer-danmaku-item`);
  743. item.classList.add(`dplayer-danmaku-${type}`);
  744. item.appendChild(content);
  745. item.style.opacity = this.danOpacity;
  746. // insert
  747. danContainer.appendChild(item);
  748. // adjust
  749. item.style.color = color;
  750. switch (type) {
  751. case 'right':
  752. item.style.top = itemHeight * getTunnel(item, type) + 'px';
  753. item.style.width = (item.offsetWidth + 1) + 'px';
  754. item.style.transform = `translateX(-${danWidth}px)`;
  755. item.addEventListener('animationend', () => {
  756. danContainer.removeChild(item);
  757. });
  758. break;
  759. case 'top':
  760. item.style.top = itemHeight * getTunnel(item, type) + 'px';
  761. item.addEventListener('animationend', () => {
  762. danContainer.removeChild(item);
  763. });
  764. break;
  765. case 'bottom':
  766. item.style.bottom = itemHeight * getTunnel(item, type) + 'px';
  767. item.addEventListener('animationend', () => {
  768. danContainer.removeChild(item);
  769. });
  770. break;
  771. default:
  772. console.error(`Can't handled danmaku type: ${type}`);
  773. }
  774. // move
  775. item.classList.add(`dplayer-danmaku-move`);
  776. };
  777. // danmaku
  778. if (this.option.danmaku) {
  779. this.danIndex = 0;
  780. const xhr = new XMLHttpRequest();
  781. xhr.onreadystatechange = () => {
  782. if (xhr.readyState === 4) {
  783. if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
  784. this.dan = JSON.parse(xhr.responseText).danmaku.sort((a, b) => a.time - b.time);
  785. // autoplay
  786. if (this.option.autoplay && !this.isMobile) {
  787. this.play();
  788. }
  789. else if (this.isMobile) {
  790. this.pause();
  791. }
  792. }
  793. else {
  794. console.log('Request was unsuccessful: ' + xhr.status);
  795. }
  796. }
  797. };
  798. xhr.open('get', this.option.danmaku.api + '?id=' + this.option.danmaku.id, true);
  799. xhr.send(null);
  800. }
  801. else {
  802. // autoplay
  803. if (this.option.autoplay && !this.isMobile) {
  804. this.play();
  805. }
  806. else if (this.isMobile) {
  807. this.pause();
  808. }
  809. }
  810. /**
  811. * comment
  812. */
  813. const commentInput = this.element.getElementsByClassName('dplayer-comment-input')[0];
  814. const commentIcon = this.element.getElementsByClassName('dplayer-comment-icon')[0];
  815. const commentBox = this.element.getElementsByClassName('dplayer-comment-box')[0];
  816. const commentSettingIcon = this.element.getElementsByClassName('dplayer-comment-setting')[0];
  817. const commentSettingBox = this.element.getElementsByClassName('dplayer-comment-setting-box')[0];
  818. const commentSendIcon = this.element.getElementsByClassName('dplayer-send-icon')[0];
  819. const sendComment = () => {
  820. // text can't be empty
  821. if (!commentInput.value.replace(/^\s+|\s+$/g, '')) {
  822. alert('要输入弹幕内容啊喂!');
  823. return;
  824. }
  825. const danmakuData = {
  826. token: this.option.danmaku.token,
  827. player: this.option.danmaku.id,
  828. author: 'DIYgod',
  829. time: this.audio.currentTime,
  830. text: commentInput.value,
  831. color: this.element.querySelector('input[name="dplayer-danmaku-color"]:checked').value,
  832. type: this.element.querySelector('input[name="dplayer-danmaku-type"]:checked').value
  833. };
  834. const xhr = new XMLHttpRequest();
  835. xhr.onreadystatechange = () => {
  836. if (xhr.readyState === 4) {
  837. if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
  838. console.log('Post danmaku: ', JSON.parse(xhr.responseText));
  839. }
  840. else {
  841. console.log('Request was unsuccessful: ' + xhr.status);
  842. }
  843. }
  844. };
  845. xhr.open('post', this.option.danmaku.api, true);
  846. xhr.send(JSON.stringify(danmakuData));
  847. commentInput.value = '';
  848. closeComment();
  849. this.dan.splice(this.danIndex, 0, danmakuData);
  850. this.danIndex++;
  851. this.danmakuIn(danmakuData.text, danmakuData.color, danmakuData.type);
  852. };
  853. const closeCommentSetting = () => {
  854. if (commentSettingBox.classList.contains('dplayer-comment-setting-open')) {
  855. commentSettingBox.classList.remove('dplayer-comment-setting-open');
  856. }
  857. };
  858. const toggleCommentSetting = () => {
  859. if (commentSettingBox.classList.contains('dplayer-comment-setting-open')) {
  860. commentSettingBox.classList.remove('dplayer-comment-setting-open');
  861. }
  862. else {
  863. commentSettingBox.classList.add('dplayer-comment-setting-open');
  864. }
  865. };
  866. let disableHide = 0;
  867. const closeComment = () => {
  868. if (commentBox.classList.contains('dplayer-comment-box-open')) {
  869. commentBox.classList.remove('dplayer-comment-box-open');
  870. mask.classList.remove('dplayer-mask-show');
  871. clearInterval(disableHide);
  872. this.element.classList.remove('dplayer-show-controller');
  873. closeCommentSetting();
  874. }
  875. };
  876. const openComment = () => {
  877. commentBox.classList.add('dplayer-comment-box-open');
  878. mask.classList.add('dplayer-mask-show');
  879. disableHide = setInterval(() => {
  880. clearTimeout(hideTime);
  881. }, 1000);
  882. this.element.classList.add('dplayer-show-controller');
  883. };
  884. mask.addEventListener('click', () => {
  885. closeComment();
  886. });
  887. commentIcon.addEventListener('click', () => {
  888. openComment();
  889. setTimeout(() => {
  890. commentInput.focus();
  891. }, 300);
  892. });
  893. commentSettingIcon.addEventListener('click', () => {
  894. toggleCommentSetting();
  895. });
  896. // comment setting box
  897. this.element.getElementsByClassName('dplayer-comment-setting-color')[0].addEventListener('click', () => {
  898. const sele = this.element.querySelector('input[name="dplayer-danmaku-color"]:checked+span');
  899. if (sele) {
  900. commentSettingIcon.setAttribute('style', sele.getAttribute('style'));
  901. }
  902. });
  903. commentInput.addEventListener('click', () => {
  904. closeCommentSetting();
  905. });
  906. commentInput.addEventListener('keydown', (e) => {
  907. const event = e || window.event;
  908. if (event.keyCode === 13) {
  909. sendComment();
  910. }
  911. });
  912. commentSendIcon.addEventListener('click', sendComment);
  913. /**
  914. * full screen
  915. */
  916. const resetAnimation = () => {
  917. danWidth = danContainer.offsetWidth;
  918. const items = this.element.getElementsByClassName('dplayer-danmaku-item');
  919. for (let i = 0; i < items.length; i++) {
  920. items[i].style.transform = `translateX(-${danWidth}px)`;
  921. }
  922. };
  923. this.element.addEventListener('fullscreenchange', () => {
  924. resetAnimation();
  925. });
  926. this.element.addEventListener('mozfullscreenchange', () => {
  927. resetAnimation();
  928. });
  929. this.element.addEventListener('webkitfullscreenchange', () => {
  930. resetAnimation();
  931. });
  932. this.element.getElementsByClassName('dplayer-full-icon')[0].addEventListener('click', () => {
  933. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
  934. if (this.element.requestFullscreen) {
  935. this.element.requestFullscreen();
  936. }
  937. else if (this.element.mozRequestFullScreen) {
  938. this.element.mozRequestFullScreen();
  939. }
  940. else if (this.element.webkitRequestFullscreen) {
  941. this.element.webkitRequestFullscreen();
  942. }
  943. }
  944. else {
  945. if (document.cancelFullScreen) {
  946. document.cancelFullScreen();
  947. }
  948. else if (document.mozCancelFullScreen) {
  949. document.mozCancelFullScreen();
  950. }
  951. else if (document.webkitCancelFullScreen) {
  952. document.webkitCancelFullScreen();
  953. }
  954. }
  955. resetAnimation();
  956. });
  957. /**
  958. * hot key
  959. */
  960. document.addEventListener('keydown', (e) => {
  961. const event = e || window.event;
  962. let percentage;
  963. switch (event.keyCode) {
  964. case 32:
  965. event.preventDefault();
  966. this.toggle();
  967. break;
  968. case 37:
  969. event.preventDefault();
  970. this.audio.currentTime = this.audio.currentTime -5;
  971. break;
  972. case 39:
  973. event.preventDefault();
  974. this.audio.currentTime = this.audio.currentTime + 5;
  975. break;
  976. case 38:
  977. event.preventDefault();
  978. percentage = this.audio.volume + 0.1;
  979. percentage = percentage > 0 ? percentage : 0;
  980. percentage = percentage < 1 ? percentage : 1;
  981. this.updateBar('volume', percentage, 'width');
  982. this.audio.volume = percentage;
  983. if (this.audio.muted) {
  984. this.audio.muted = false;
  985. }
  986. switchVolumeIcon();
  987. break;
  988. case 40:
  989. event.preventDefault();
  990. percentage = this.audio.volume - 0.1;
  991. percentage = percentage > 0 ? percentage : 0;
  992. percentage = percentage < 1 ? percentage : 1;
  993. this.updateBar('volume', percentage, 'width');
  994. this.audio.volume = percentage;
  995. if (this.audio.muted) {
  996. this.audio.muted = false;
  997. }
  998. switchVolumeIcon();
  999. break;
  1000. }
  1001. });
  1002. /**
  1003. * right key
  1004. */
  1005. this.menu = this.element.getElementsByClassName('dplayer-menu')[0];
  1006. this.element.addEventListener('contextmenu', (e) => {
  1007. const event = e || window.event;
  1008. event.preventDefault();
  1009. this.menu.style.left = event.clientX - this.element.getBoundingClientRect().left + 'px';
  1010. this.menu.style.top = event.clientY - this.element.getBoundingClientRect().top + 'px';
  1011. this.menu.classList.add('dplayer-menu-show');
  1012. mask.classList.add('dplayer-mask-show');
  1013. mask.addEventListener('click', () => {
  1014. mask.classList.remove('dplayer-mask-show');
  1015. this.menu.classList.remove('dplayer-menu-show');
  1016. });
  1017. });
  1018. }
  1019. /**
  1020. * Play music
  1021. */
  1022. play() {
  1023. if (this.audio.paused) {
  1024. this.shouldpause = false;
  1025. this.bezel.innerHTML = this.getSVG('play');
  1026. this.bezel.classList.add('dplayer-bezel-transition');
  1027. this.playButton.innerHTML = this.getSVG('pause');
  1028. this.audio.play();
  1029. if (this.playedTime) {
  1030. clearInterval(this.playedTime);
  1031. }
  1032. this.setTime();
  1033. this.element.classList.add('dplayer-playing');
  1034. this.trigger('play');
  1035. }
  1036. }
  1037. /**
  1038. * Pause music
  1039. */
  1040. pause() {
  1041. if (!this.shouldpause || this.ended) {
  1042. this.shouldpause = true;
  1043. this.element.classList.remove('dplayer-loading');
  1044. this.bezel.innerHTML = this.getSVG('pause');
  1045. this.bezel.classList.add('dplayer-bezel-transition');
  1046. this.ended = false;
  1047. this.playButton.innerHTML = this.getSVG('play');
  1048. this.audio.pause();
  1049. clearInterval(this.playedTime);
  1050. this.element.classList.remove('dplayer-playing');
  1051. this.trigger('pause');
  1052. }
  1053. }
  1054. /**
  1055. * attach event
  1056. */
  1057. on(name, func) {
  1058. if (typeof func === 'function') {
  1059. this.event[name].push(func);
  1060. }
  1061. }
  1062. }
  1063. if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
  1064. module.exports = DPlayer;
  1065. }
  1066. else {
  1067. window.DPlayer = DPlayer;
  1068. }
  1069. })();