controller.js 17 KB

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