controller.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import utils from './utils';
  2. import Thumbnails from './thumbnails';
  3. import Icons from './icons';
  4. import { Subject } from 'rxjs';
  5. let cast;
  6. let runOnce = true;
  7. let isCasting = false;
  8. class Controller {
  9. constructor(player) {
  10. this.player = player;
  11. this.autoHideTimer = 0;
  12. if (!utils.isMobile) {
  13. this.player.container.addEventListener('mousemove', () => {
  14. this.setAutoHide();
  15. });
  16. this.player.container.addEventListener('click', () => {
  17. this.setAutoHide();
  18. });
  19. this.player.on('play', () => {
  20. this.setAutoHide();
  21. });
  22. this.player.on('pause', () => {
  23. this.setAutoHide();
  24. });
  25. }
  26. this.initPlayButton();
  27. this.initThumbnails();
  28. this.initPlayedBar();
  29. this.initFullButton();
  30. this.initQualityButton();
  31. this.initScreenshotButton();
  32. this.initSubtitleButton();
  33. this.initHighlights();
  34. this.initAirplayButton();
  35. this.initChromecastButton();
  36. if (!utils.isMobile) {
  37. this.initVolumeButton();
  38. }
  39. }
  40. initPlayButton() {
  41. this.player.template.playButton.addEventListener('click', () => {
  42. this.player.toggle();
  43. });
  44. this.player.template.mobilePlayButton.addEventListener('click', () => {
  45. this.player.toggle();
  46. });
  47. if (!utils.isMobile) {
  48. this.player.template.videoWrap.addEventListener('click', () => {
  49. this.player.toggle();
  50. });
  51. this.player.template.controllerMask.addEventListener('click', () => {
  52. this.player.toggle();
  53. });
  54. } else {
  55. this.player.template.videoWrap.addEventListener('click', () => {
  56. this.toggle();
  57. });
  58. this.player.template.controllerMask.addEventListener('click', () => {
  59. this.toggle();
  60. });
  61. }
  62. }
  63. initHighlights() {
  64. this.player.on('durationchange', () => {
  65. if (this.player.video.duration !== 1 && this.player.video.duration !== Infinity) {
  66. if (this.player.options.highlight) {
  67. const highlights = document.querySelectorAll('.dplayer-highlight');
  68. [].slice.call(highlights, 0).forEach((item) => {
  69. this.player.template.playedBarWrap.removeChild(item);
  70. });
  71. for (let i = 0; i < this.player.options.highlight.length; i++) {
  72. if (!this.player.options.highlight[i].text || !this.player.options.highlight[i].time) {
  73. continue;
  74. }
  75. const p = document.createElement('div');
  76. p.classList.add('dplayer-highlight');
  77. p.style.left = (this.player.options.highlight[i].time / this.player.video.duration) * 100 + '%';
  78. p.innerHTML = '<span class="dplayer-highlight-text">' + this.player.options.highlight[i].text + '</span>';
  79. this.player.template.playedBarWrap.insertBefore(p, this.player.template.playedBarTime);
  80. }
  81. }
  82. }
  83. });
  84. }
  85. initThumbnails() {
  86. if (this.player.options.video.thumbnails) {
  87. this.thumbnails = new Thumbnails({
  88. container: this.player.template.barPreview,
  89. barWidth: this.player.template.barWrap.offsetWidth,
  90. url: this.player.options.video.thumbnails,
  91. events: this.player.events,
  92. });
  93. this.player.on('loadedmetadata', () => {
  94. this.thumbnails.resize(160, (this.player.video.videoHeight / this.player.video.videoWidth) * 160, this.player.template.barWrap.offsetWidth);
  95. });
  96. }
  97. }
  98. initPlayedBar() {
  99. const thumbMove = (e) => {
  100. let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.playedBarWrap)) / this.player.template.playedBarWrap.clientWidth;
  101. percentage = Math.max(percentage, 0);
  102. percentage = Math.min(percentage, 1);
  103. this.player.bar.set('played', percentage, 'width');
  104. this.player.template.ptime.innerHTML = utils.secondToTime(percentage * this.player.video.duration);
  105. };
  106. const thumbUp = (e) => {
  107. document.removeEventListener(utils.nameMap.dragEnd, thumbUp);
  108. document.removeEventListener(utils.nameMap.dragMove, thumbMove);
  109. let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.playedBarWrap)) / this.player.template.playedBarWrap.clientWidth;
  110. percentage = Math.max(percentage, 0);
  111. percentage = Math.min(percentage, 1);
  112. this.player.bar.set('played', percentage, 'width');
  113. this.player.seek(this.player.bar.get('played') * this.player.video.duration);
  114. this.player.timer.enable('progress');
  115. };
  116. this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragStart, () => {
  117. this.player.timer.disable('progress');
  118. document.addEventListener(utils.nameMap.dragMove, thumbMove);
  119. document.addEventListener(utils.nameMap.dragEnd, thumbUp);
  120. });
  121. this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragMove, (e) => {
  122. if (this.player.video.duration) {
  123. const px = this.player.template.playedBarWrap.getBoundingClientRect().left;
  124. const tx = (e.clientX || e.changedTouches[0].clientX) - px;
  125. if (tx < 0 || tx > this.player.template.playedBarWrap.offsetWidth) {
  126. return;
  127. }
  128. const time = this.player.video.duration * (tx / this.player.template.playedBarWrap.offsetWidth);
  129. if (utils.isMobile) {
  130. this.thumbnails && this.thumbnails.show();
  131. }
  132. this.thumbnails && this.thumbnails.move(tx);
  133. this.player.template.playedBarTime.style.left = `${tx - (time >= 3600 ? 25 : 20)}px`;
  134. this.player.template.playedBarTime.innerText = utils.secondToTime(time);
  135. this.player.template.playedBarTime.classList.remove('hidden');
  136. }
  137. });
  138. this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragEnd, () => {
  139. if (utils.isMobile) {
  140. this.thumbnails && this.thumbnails.hide();
  141. }
  142. });
  143. if (!utils.isMobile) {
  144. this.player.template.playedBarWrap.addEventListener('mouseenter', () => {
  145. if (this.player.video.duration) {
  146. this.thumbnails && this.thumbnails.show();
  147. this.player.template.playedBarTime.classList.remove('hidden');
  148. }
  149. });
  150. this.player.template.playedBarWrap.addEventListener('mouseleave', () => {
  151. if (this.player.video.duration) {
  152. this.thumbnails && this.thumbnails.hide();
  153. this.player.template.playedBarTime.classList.add('hidden');
  154. }
  155. });
  156. }
  157. }
  158. initFullButton() {
  159. this.player.template.browserFullButton.addEventListener('click', () => {
  160. this.player.fullScreen.toggle('browser');
  161. });
  162. this.player.template.webFullButton.addEventListener('click', () => {
  163. this.player.fullScreen.toggle('web');
  164. });
  165. }
  166. initVolumeButton() {
  167. const vWidth = 35;
  168. const volumeMove = (event) => {
  169. const e = event || window.event;
  170. const percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.volumeBarWrap) - 5.5) / vWidth;
  171. this.player.volume(percentage);
  172. };
  173. const volumeUp = () => {
  174. document.removeEventListener(utils.nameMap.dragEnd, volumeUp);
  175. document.removeEventListener(utils.nameMap.dragMove, volumeMove);
  176. this.player.template.volumeButton.classList.remove('dplayer-volume-active');
  177. };
  178. this.player.template.volumeBarWrapWrap.addEventListener('click', (event) => {
  179. const e = event || window.event;
  180. const percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.volumeBarWrap) - 5.5) / vWidth;
  181. this.player.volume(percentage);
  182. });
  183. this.player.template.volumeBarWrapWrap.addEventListener(utils.nameMap.dragStart, () => {
  184. document.addEventListener(utils.nameMap.dragMove, volumeMove);
  185. document.addEventListener(utils.nameMap.dragEnd, volumeUp);
  186. this.player.template.volumeButton.classList.add('dplayer-volume-active');
  187. });
  188. this.player.template.volumeButtonIcon.addEventListener('click', () => {
  189. if (this.player.video.muted) {
  190. this.player.video.muted = false;
  191. this.player.switchVolumeIcon();
  192. this.player.bar.set('volume', this.player.volume(), 'width');
  193. } else {
  194. this.player.video.muted = true;
  195. this.player.template.volumeIcon.innerHTML = Icons.volumeOff;
  196. this.player.bar.set('volume', 0, 'width');
  197. }
  198. });
  199. }
  200. initQualityButton() {
  201. if (this.player.options.video.quality) {
  202. this.player.template.qualityList.addEventListener('click', (e) => {
  203. if (e.target.classList.contains('dplayer-quality-item')) {
  204. this.player.switchQuality(e.target.dataset.index);
  205. }
  206. });
  207. }
  208. }
  209. initScreenshotButton() {
  210. if (this.player.options.screenshot) {
  211. this.player.template.camareButton.addEventListener('click', () => {
  212. const canvas = document.createElement('canvas');
  213. canvas.width = this.player.video.videoWidth;
  214. canvas.height = this.player.video.videoHeight;
  215. canvas.getContext('2d').drawImage(this.player.video, 0, 0, canvas.width, canvas.height);
  216. let dataURL;
  217. canvas.toBlob((blob) => {
  218. dataURL = URL.createObjectURL(blob);
  219. const link = document.createElement('a');
  220. link.href = dataURL;
  221. link.download = 'DPlayer.png';
  222. link.style.display = 'none';
  223. document.body.appendChild(link);
  224. link.click();
  225. document.body.removeChild(link);
  226. URL.revokeObjectURL(dataURL);
  227. });
  228. this.player.events.trigger('screenshot', dataURL);
  229. });
  230. }
  231. }
  232. initAirplayButton() {
  233. if (this.player.options.airplay) {
  234. if (window.WebKitPlaybackTargetAvailabilityEvent) {
  235. this.player.video.addEventListener(
  236. 'webkitplaybacktargetavailabilitychanged',
  237. function (event) {
  238. switch (event.availability) {
  239. case 'available':
  240. this.template.airplayButton.disable = false;
  241. break;
  242. default:
  243. this.template.airplayButton.disable = true;
  244. }
  245. this.template.airplayButton.addEventListener(
  246. 'click',
  247. function () {
  248. this.video.webkitShowPlaybackTargetPicker();
  249. }.bind(this)
  250. );
  251. }.bind(this.player)
  252. );
  253. } else {
  254. this.player.template.airplayButton.style.display = 'none';
  255. }
  256. }
  257. }
  258. initChromecast() {
  259. const script = window.document.createElement('script');
  260. script.setAttribute('type', 'text/javascript');
  261. script.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1');
  262. window.document.body.appendChild(script);
  263. window.__onGCastApiAvailable = (isAvailable) => {
  264. if (isAvailable) {
  265. cast = window.chrome.cast;
  266. const sessionRequest = new cast.SessionRequest(cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);
  267. const apiConfig = new cast.ApiConfig(
  268. sessionRequest,
  269. () => {},
  270. (status) => {
  271. if (status === cast.ReceiverAvailability.AVAILABLE) {
  272. console.log('chromecast: ', status);
  273. }
  274. }
  275. );
  276. cast.initialize(apiConfig, () => {});
  277. }
  278. };
  279. }
  280. initChromecastButton() {
  281. if (this.player.options.chromecast) {
  282. if (runOnce) {
  283. runOnce = false;
  284. this.initChromecast();
  285. }
  286. const discoverDevices = () => {
  287. const subj = new Subject();
  288. cast.requestSession(
  289. (s) => {
  290. this.session = s;
  291. subj.next('CONNECTED');
  292. launchMedia(this.player.options.video.url);
  293. },
  294. (err) => {
  295. if (err.code === 'cancel') {
  296. this.session = undefined;
  297. subj.next('CANCEL');
  298. } else {
  299. console.error('Error selecting a cast device', err);
  300. }
  301. }
  302. );
  303. return subj;
  304. };
  305. const launchMedia = (media) => {
  306. const mediaInfo = new cast.media.MediaInfo(media);
  307. const request = new cast.media.LoadRequest(mediaInfo);
  308. if (!this.session) {
  309. window.open(media);
  310. return false;
  311. }
  312. this.session.loadMedia(request, onMediaDiscovered.bind(this, 'loadMedia'), onMediaError).play();
  313. return true;
  314. };
  315. const onMediaDiscovered = (how, media) => {
  316. this.currentMedia = media;
  317. };
  318. const onMediaError = (err) => {
  319. console.error('Error launching media', err);
  320. };
  321. this.player.template.chromecastButton.addEventListener('click', () => {
  322. if (isCasting) {
  323. isCasting = false;
  324. this.currentMedia.stop();
  325. this.session.stop();
  326. this.initChromecast();
  327. } else {
  328. isCasting = true;
  329. discoverDevices();
  330. }
  331. });
  332. }
  333. }
  334. initSubtitleButton() {
  335. if (this.player.options.subtitle) {
  336. this.player.events.on('subtitle_show', () => {
  337. this.player.template.subtitleButton.dataset.balloon = this.player.tran('Hide subtitle');
  338. this.player.template.subtitleButtonInner.style.opacity = '';
  339. this.player.user.set('subtitle', 1);
  340. });
  341. this.player.events.on('subtitle_hide', () => {
  342. this.player.template.subtitleButton.dataset.balloon = this.player.tran('Show subtitle');
  343. this.player.template.subtitleButtonInner.style.opacity = '0.4';
  344. this.player.user.set('subtitle', 0);
  345. });
  346. this.player.template.subtitleButton.addEventListener('click', () => {
  347. this.player.subtitle.toggle();
  348. });
  349. }
  350. }
  351. setAutoHide() {
  352. this.show();
  353. clearTimeout(this.autoHideTimer);
  354. this.autoHideTimer = setTimeout(() => {
  355. if (this.player.video.played.length && !this.player.paused && !this.disableAutoHide) {
  356. this.hide();
  357. }
  358. }, 3000);
  359. }
  360. show() {
  361. this.player.container.classList.remove('dplayer-hide-controller');
  362. }
  363. hide() {
  364. this.player.container.classList.add('dplayer-hide-controller');
  365. this.player.setting.hide();
  366. this.player.comment && this.player.comment.hide();
  367. }
  368. isShow() {
  369. return !this.player.container.classList.contains('dplayer-hide-controller');
  370. }
  371. toggle() {
  372. if (this.isShow()) {
  373. this.hide();
  374. } else {
  375. this.show();
  376. }
  377. }
  378. destroy() {
  379. clearTimeout(this.autoHideTimer);
  380. }
  381. }
  382. export default Controller;