4chan \ IB Simple Web Media Player

Simple Web Media Player for 4chan and other imageboards.

当前为 2022-09-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 4chan \ IB Simple Web Media Player
  3. // @description Simple Web Media Player for 4chan and other imageboards.
  4. // @namespace LabMember-001
  5. // @author Hououin Kyōma
  6. // @license GPLv3
  7. // @version 1.4.10
  8.  
  9. // @grant none
  10. // @run-at document-end
  11.  
  12. // @match https://*.4chan.org/*
  13. // @match https://*.4channel.org/*
  14. // @match https://*.smuglo.li/*
  15. // @match https://*.smugloli.net/*
  16. // @match https://*.kissu.moe/*
  17. // @match https://*.4taba.net/*
  18. // @match https://*.2kind.moe/*
  19. // @match https://*.1chan.net/*
  20. // @match https://*.otterchat.net/*
  21. // @match https://*.fatchan.org/*
  22. // @match https://*.7chan.org/*
  23. // @match https://*.420chan.org/*
  24. // @match https://*.anon.cafe/*
  25. // @match https://*.4-ch.net/*
  26. // @match https://*.sushigirl.us/*
  27. // @match https://*.uboachan.net/*
  28.  
  29. // @match https://*.archived.moe/*
  30. // @match https://*.desuarchive.org/*
  31. // @match https://*.4plebs.org/*
  32. // @match https://*.warosu.org/*
  33.  
  34. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAilBMVEX///8zGeTw8PArQKsXuQAiAv+kpKTu6tkAAADd3d0vEP8fHx9hTOF/bPjj3/97enb6+ffx7//v7+/y7+KUkof6+ftsWeDKwttYQP89If+Ec+5WR7ff398dHRv7+viom94+JuPBvrBKMuIODg3+/f1tbGl0cnZlUepjWK+NfPVPO9SIeej6+vtAQEBg9CwMAAAA3ElEQVRIx+3UxxKCMBCA4bUggahIt/de3v/1HEiICe44iwM3/pPM5jtgVsGuHLTku3TfUW03JJI8YlDFIYUMn6A1iSgEjFhdZGB0zw6Kjzkxhor09XrZQScvJ45eS8SXbXQp3Yv5WNftJ9VJejTWkkLscK2JE4nYuxVTHaL2T6kWMppZSO4NmUoymo4Byb+ekakgLioAAo5MBbGwJVcbKacykzD2BykjEjERkeiITD6oAilQs6T512/+KisvzM+17MqK5ffx5V9yZCrIfBGg4uUhU/lD9jhD4h4yfQMAvzcdsmcBygAAAABJRU5ErkJggg==
  35.  
  36. // ==/UserScript==
  37.  
  38. // Most of the sites above are to test scripts.
  39.  
  40. console.log('Loading IB Media Player.');
  41.  
  42. /* Repository: */
  43. /* https://github.com/LabMember-001/Simple-Web-Media-Player */
  44. /* https://labmember-001.github.io/Simple-Web-Media-Player/ */
  45. /* */
  46. /* */
  47. /* */
  48.  
  49. /* == BOARD SUPPORT ==
  50. // Script will alter behavior on different imageboard backends
  51. // When adding custom board scripts, alter board behavior on bottom of the script. You'll need some knowledge on JS.
  52. // In most cases, simply adding an @match to the top of this script to turn it on for that domain will work
  53. // as it captures all link presses and checks for webm.
  54. // If you want more compatability, check the issue tracker for existing requests and if there are none, you may request help.
  55. */
  56.  
  57. /* == SCRIPT CONFIGURATION ==
  58. // To define a board as a precoded backend type (vichan, tinyib, lynxchan, etc) simply
  59. // add a regex for that domain in the swmpBoards category below these comments.
  60. */
  61.  
  62. /* == TO DO ==
  63. //
  64. // High priority
  65. // - Resizing
  66. //
  67. // Medium Priority
  68. // - Loading poster for large files
  69. // - Make window move stop if you release mouseup outside of the screen.
  70. // - Automatic backend script detection.
  71. //
  72. // Low Priority
  73. // - Title length
  74. // - Fix support for archived.moe redirection urls.
  75. // - Fix titles on some backends. (Mostly just vichan+4chan taking original filename right now).
  76. */
  77.  
  78. /* == Current Script Support ==
  79. // Yotsuba (4chan)
  80. // Tinyboard/Vichan/Infinity
  81. // Fuuka
  82. // TinyIB
  83. // Wakaba
  84. // FoolFuuka (Not archived.moe's redirects, but 4plebs is fine).
  85. // Kusaba
  86. */
  87.  
  88. /* == Script Specific Bugs ==
  89. // - Vichan shows original title when clicked on video thumb, but not link.
  90. // - Doesnt grab titles on Fuuka, TinyIB, Wakaba, FoolFuuka
  91. //
  92. */
  93.  
  94. /*
  95. // == Missing Script Support ==
  96. // Some Kusaba sites have issues loading video when clicked on thumbnail. Links typically work. 7chan is fine.
  97. // Phutaba has a slight bug with videos which shows file information when hovering over player depending on which link you click.
  98. // Some of the bigger Lynxchan sites are very modified and have no thumbnail click support.
  99. // jschan and vichan will not properly work if not added to the board specific types. Should be fixable for me later.
  100. // InfinityNext does not work. Don't care.
  101. */
  102.  
  103. // Get domain
  104. var currentUrl = window.location.href;
  105.  
  106. // Board Types - probably temporary.
  107. var swmpBoards = {
  108. fourchan: // For title support, otherwise works well without adding here.
  109. ['https:\/\/*..*(4chan|4channel).org\/*'],
  110. vichan: // For Title and thumbnail click support. Not great without due to thumbnails.
  111. ['https:\/\/*..*(smuglo.li|smugloli.net)\/*',
  112. 'https:\/\/*..*2kind.moe\/*',
  113. 'https:\/\/*..*kissu.moe\/*',
  114. 'https:\/\/*..*sushigirl.us\/*',
  115. 'https:\/\/*..*uboachan.net\/*'],
  116. tinyib: // Title support not written, otherwise works well without adding here.
  117. ['https:\/\/*..*1chan.net\/*'],
  118. wakaba: // Title support not written, otherwise works well without adding here.
  119. ['https:\/\/*..*otterchat.net\/*'],
  120. lynxchan: // Title support not written, otherwise works well without adding here.
  121. ['https:\/\/*..*anon.cafe\/*'],
  122. jschan: // Title support not written. Jschan installations *must* be added here to work properly.
  123. ['https:\/\/*..*fatchan.org\/*']
  124. }
  125.  
  126. // regex to check the backend script of current domain
  127. var backendScript = [];
  128. Object.keys(swmpBoards).forEach(script => {
  129. swmpBoards[script].forEach(regex => {
  130. if (currentUrl.match(regex)) {
  131. backendScript = script;
  132. }
  133. });
  134. });
  135.  
  136. if (backendScript.length != 0) {
  137. console.log(backendScript);
  138. }
  139.  
  140.  
  141. // This configuration variable can be overwritten wherever you want later
  142. // on as you wish or add your own site variables for user configuration.
  143. var swmpConfig = {
  144. autoplay: 'true', // Autoplay media when launched by SWMP.
  145. loop: 'true', // Loop media when launched by SWMP.
  146. windowed: 'true',
  147. positionTop: '100',
  148. positionOffset: '100',
  149. positionSide: 'right',
  150. volume: 60,
  151. volumeScroll: 'true',
  152. muted: 'false', //Not implemented
  153. skip: 5, //skip forward/backward keyboard shortcut, seconds.
  154. theme: 'default', //Default theme
  155. themes: //All themes
  156. [
  157. ['default', 'MPC Light'],
  158. ['dark', 'MPC Dark'],
  159. ['kurisu', 'Kurisumasu'],
  160. ['winxp', 'WIN_XP'],
  161. ['modernity', 'Modernity']
  162. ],
  163. files: 'avi|mpeg|mpg|ogv|mp4|webm|flv|wav|mp3|m4a|mp2|ogg|flac',
  164. allowMultiple: 'false',
  165. downloadAttribute: 'true', //true = override default action, false = download
  166. doubleclickMaximize: 'false' //true = doubleclick video to maximize instead of fullscreen
  167. }
  168.  
  169. // swmpConfig.nameLikeThis, localStorage.swmpNameLikeThis
  170. // Converted like this: localStorage.swmpVolume, swmpConfig.volume
  171. // Important: Converts first character in storage after swmp to uppercase!
  172. var storageValues = [
  173. 'volume',
  174. 'theme',
  175. 'autoplay',
  176. 'loop',
  177. 'allowMultiple',
  178. 'windowed',
  179. 'muted',
  180. 'volumeScroll',
  181. 'downloadAttribute',
  182. 'doubleclickMaximize'
  183. ];
  184.  
  185. function upperFirst(string) {
  186. return string.charAt(0).toUpperCase() + string.slice(1)
  187. }
  188.  
  189. function initStorage(item) {
  190. var local_item = `swmp${upperFirst(item)}`;
  191. var config_item = item;
  192. if (localStorage.getItem(`${local_item}`) == undefined) {
  193. localStorage.setItem(`${local_item}`, swmpConfig[`${config_item}`]);
  194. } else {
  195. swmpConfig[`${config_item}`] = localStorage.getItem(`${local_item}`);
  196. }
  197. }
  198.  
  199. storageValues.forEach( (item) => {
  200. initStorage(item);
  201. });
  202.  
  203.  
  204. // Add style to head when DOM is loaded.
  205.  
  206. if (!document.getElementById('swmp-stylesheet')) { // Don't bother injecting style on demo page.
  207. var swmpStyle = document.createElement('style');
  208. swmpStyle.setAttribute('id', 'swmp-stylesheet');
  209. swmpStyle.innerHTML = `div.swmp,div.swmp.swmp-theme-dark,div.swmp.swmp-theme-dark *{--swmp-container-border:#000;--swmp-controls-background:var(--swmp-background);--swmp-button-background:var(--swmp-background);--swmp-seek-height:30px;--swmp-seek-offset:10px;--swmp-btn-border-right:#000;--swmp-btn-border-bottom:#000;--swmp-btn-border-left-active:#000;--swmp-btn-border-top-active:#000}.swmp *,div.swmp.swmp-container{font-family:-apple-system,BlinkMacSystemFont,URW Gothic,MS PGothic,Helvetica,sans-serif;font-size:11pt;text-indent:4px;letter-spacing:1px;line-height:1;outline:0;color:var(--swmp-text-color)}div.swmp iframe,div.swmp video{max-width:500px;max-height:500px;width:auto;height:auto;margin:auto}.swmp *,div.swmp.swmp-container,select.swmp.swmp-selector{color:var(--swmp-text-color)}div.swmp,div.swmp.swmp-theme-dark,div.swmp.swmp-theme-dark *,div.swmp.swmp-theme-winxp,div.swmp.swmp-theme-winxp *{--swmp-range-thumb-border-right:#000;--swmp-range-thumb-border-bottom:#000}.swmp *{background:0 0;border:0;margin:0;padding:0;height:unset;width:unset;font-weight:400}div.swmp{--swmp-background:#e6e6e6;--swmp-container-border-radius:0;--swmp-container-box-shadow:none;--swmp-player-container-background:#000;--swmp-player-container-border-left:#000;--swmp-player-container-border-top:#000;--swmp-player-container-border-right:#000;--swmp-player-container-border-bottom:#000;--swmp-settings-background:var(--swmp-controls-background);--swmp-text-color:#000;--swmp-button-mask-color:#000;--swmp-seek-background:var(--swmp-controls-background);--swmp-seek-progress-color:lightgrey;--swmp-seek-border-left:darkgray;--swmp-seek-border-top:darkgray;--swmp-seek-border-right:#fff;--swmp-seek-border-bottom:#fff;--swmp-range-thumb-color:var(--swmp-controls-background);--swmp-range-thumb-border-left:#fff;--swmp-range-thumb-border-top:#fff;--swmp-btn-border-radius:0;--swmp-btn-border-left:#fff;--swmp-btn-border-top:#fff;--swmp-btn-border-right-active:#fff;--swmp-btn-border-bottom-active:#fff}div.swmp.swmp-theme-dark,div.swmp.swmp-theme-dark *{--swmp-background:#333;--swmp-text-color:#888;--swmp-button-mask-color:#888;--swmp-seek-progress-color:#555;--swmp-seek-border-left:#1a1a1a;--swmp-seek-border-top:#1a1a1a;--swmp-seek-border-right:#464646;--swmp-seek-border-bottom:#464646;--swmp-range-thumb-color:var(--swmp-background);--swmp-range-thumb-border-left:#777;--swmp-range-thumb-border-top:#777;--swmp-btn-border-left:#777;--swmp-btn-border-top:#777;--swmp-btn-border-right-active:#777;--swmp-btn-border-bottom-active:#777}div.swmp.swmp-theme-kurisu,div.swmp.swmp-theme-kurisu *{--swmp-background:#a44242;--swmp-text-color:#fff;--swmp-button-mask-color:#fff}div.swmp.swmp-container{position:relative;display:inline-flex;flex-direction:column;padding:2px;background:var(--swmp-background);border:1px solid var(--swmp-container-border);border-radius:var(--swmp-container-border-radius);z-index:100;overflow:hidden;min-width:320px;width:auto;box-shadow:var(--swmp-container-box-shadow)}div.swmp .swmp-player-container,div.swmp audio,div.swmp iframe,div.swmp video{background:var(--swmp-player-container-background)}div.swmp.swmp-window.swmp-window-container{display:flex;justify-content:space-between;padding-bottom:2px}div.swmp.swmp-fullscreen div.swmp-settings.swmp-settings-container,div.swmp.swmp-fullscreen div.swmp-window.swmp-window-container,div.swmp.swmp-minimized div.swmp-player-container{display:none}div.swmp.swmp-fullscreen div.swmp-player-container{display:block;width:100vw;height:100vh}span.swmp.swmp-window.swmp-window-titlebar{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none;position:relative;margin:auto;cursor:move;width:100%;text-align:center;display:block}span.swmp.swmp-window.swmp-window-title{display:block;max-width:260px;text-overflow:ellipsis;overflow:hidden;margin:auto auto -2px;line-height:1.5}span.swmp.swmp-window.swmp-window-buttons-contain{display:flex;flex:0 1 auto;margin-right:-2px}div.swmp .swmp-player-container{display:flex;position:relative;height:100%;border:1px solid;border-left-color:var(--swmp-player-container-border-left);border-top-color:var(--swmp-player-container-border-top);border-right-color:var(--swmp-player-container-border-right);border-bottom-color:var(--swmp-player-container-border-bottom)}div.swmp.swmp-audio div.swmp-player-container,div.swmp.swmp-fullscreen div.swmp-player-container{border:none}div.swmp.swmp-container.swmp-fullscreen{background:#000;position:unset!important;width:100%;height:auto;border-radius:0!important}.swmp.swmp-container.swmp-maximized{position:fixed!important;top:0!important;bottom:0!important;right:0!important;left:0!important;transition:.2s ease-out;border-radius:0!important}div.swmp.swmp-container.swmp-maximized audio,div.swmp.swmp-container.swmp-maximized iframe,div.swmp.swmp-container.swmp-maximized video{position:absolute;max-width:100%;max-height:100%;width:100%;height:100%;top:0;bottom:0;left:0;right:0}div.swmp.swmp-container.swmp-fullscreen audio,div.swmp.swmp-container.swmp-fullscreen iframe,div.swmp.swmp-container.swmp-fullscreen video{position:relative;width:100%;height:100%;max-width:100%;max-height:100%}div.swmp iframe{min-width:420px;min-height:236px;pointer-events:none}div.swmp audio{min-width:320px;min-height:40px}div.swmp.swmp-controls{bottom:0;left:0;background:var(--swmp-controls-background);width:100%;display:flex;flex-direction:column}div.swmp.swmp-settings-container{background:var(--swmp-settings-background);display:flex;flex-direction:column;align-items:flex-start}div.swmp.swmp-fullscreen div.swmp.swmp-controls{position:absolute;opacity:0;transition:opacity .5s ease-out}div.swmp.swmp-fullscreen.swmp-movingmouse div.swmp.swmp-controls{opacity:1;position:absolute;transition:none}div.swmp.swmp-container .swmp-player-container{cursor:none}div.swmp.swmp-movingmouse .swmp-player-container{cursor:unset}div.swmp.swmp-controls span.swmp-seek-container{display:flex;width:calc(100% - 6px);height:var(--swmp-seek-height);margin:auto;position:relative}div.swmp.swmp-controls span.swmp-seek-container input.swmp.swmp-seeker{width:calc(100% - var(--swmp-seek-offset));left:calc(var(--swmp-seek-offset)/ 2);-webkit-appearance:none;background:#0000;padding:0;margin:0;height:var(--swmp-seek-height);position:absolute;z-index:1;cursor:pointer;-webkit-margin-top:-14px}input[type=range].swmp{background:0 0!important;border:0!important;outline:0!important}span.swmp input[type=range]::-webkit-slider-runnable-track{width:100%;height:8px;cursor:pointer;background:#0000;border-radius:0;border:1px solid #000;border-left-color:var(--swmp-seek-border-left);border-top-color:var(--swmp-seek-border-top);border-right-color:var(--swmp-seek-border-right);border-bottom-color:var(--swmp-seek-border-bottom)}span.swmp input[type=range]::-moz-range-track{width:100%;height:6px;cursor:pointer;background:#0000;border-radius:0;border:1px solid #000;border-left-color:var(--swmp-seek-border-left);border-top-color:var(--swmp-seek-border-top);border-right-color:var(--swmp-seek-border-right);border-bottom-color:var(--swmp-seek-border-bottom)}span.swmp progress::-webkit-progress-bar{background:#0000}span.swmp progress::-webkit-progress-value{background:var(--swmp-seek-progress-color)}span.swmp progress::-moz-progress-bar{background:var(--swmp-seek-progress-color)}span.swmp input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:4px solid var(--swmp-range-thumb-color);height:8px;width:2px;border-radius:0;padding:4px 3px;background:#0000;background:linear-gradient(180deg,var(--swmp-seek-border-top) 10%,var(--swmp-seek-background) 10%,var(--swmp-seek-background) 90%,var(--swmp-seek-border-bottom) 90%);cursor:pointer;margin-top:-6px;box-shadow:1px 1px 0 0 var(--swmp-range-thumb-border-bottom),-1px -1px 0 0 var(--swmp-range-thumb-border-top)}span.swmp input[type=range]::-moz-range-thumb{appearance:none;border:4px solid var(--swmp-range-thumb-color);height:8px;width:6px;border-radius:0;background:linear-gradient(180deg,var(--swmp-seek-border-top) 10%,var(--swmp-seek-background) 10%,var(--swmp-seek-background) 90%,var(--swmp-seek-border-bottom) 90%);cursor:pointer;box-shadow:1px 1px 0 0 var(--swmp-range-thumb-border-bottom),-1px -1px 0 0 var(--swmp-range-thumb-border-top)}span.swmp input[type=range]::-webkit-range-progress{height:6px;background-color:#0000}span.swmp input[type=range]::-moz-range-progress{height:6px;background-color:#0000}span.swmp progress.swmp-volume{width:50px;position:absolute;height:6px;right:8px;border:none;bottom:7px;z-index:0;background:var(--swmp-seek-background)}span.swmp input.swmp-volume[type=range]{-webkit-appearance:none;background:#0000;padding:0;margin-left:2px;cursor:pointer;position:relative}span.swmp input.swmp-volume[type=range]::-webkit-slider-thumb{padding:4px 2px;width:2px}span.swmp input.swmp-volume[type=range]::-moz-range-thumb{padding:0 1px;width:1px}div.swmp.swmp-controls span.swmp-seek-container progress.swmp-progress{width:calc(100% - var(--swmp-seek-offset));left:calc(var(--swmp-seek-offset)/ 2);height:8px;z-index:0;position:relative;background:var(--swmp-seek-background);border:none;margin:auto 0}span.swmp.swmp-row-bottom{display:flex;flex-direction:row;height:20px;margin-bottom:2px}div.swmp.swmp-fullscreen span.swmp.swmp-row-bottom{padding-bottom:5px}span.swmp.swmp-buttons-container{height:100%}select.swmp.swmp-selector{-webkit-appearance:none;appearance:none;background:var(--swmp-button-background);border:1px solid var(--swmp-text-color);outline:0;border-radius:0;width:85px;overflow:hidden;text-overflow:ellipsis;letter-spacing:0;text-indent:0}label.swmp.swmp-settings{display:inline-flex;flex-direction:row-reverse;line-height:1.2}input.swmp.swmp-settings{margin:-2px 0 0 4px;border:1px solid var(--swmp-text-color);appearance:none;-webkit-appearance:none;outline:0;width:14px;height:14px;background:var(--swmp-controls-background)}input.swmp.swmp-settings:checked{outline:5px inset var(--swmp-text-color);outline-offset:-8px}button.swmp.swmp-button{min-width:26px;height:100%;padding:0 4px;display:inline-block;cursor:pointer;margin:0 2px;color:var(--swmp-button-mask-color);background:var(--swmp-button-background);border:1px solid;border-radius:var(--swmp-btn-border-radius);border-bottom-color:var(--swmp-btn-border-bottom);border-right-color:var(--swmp-btn-border-right);border-top-color:var(--swmp-btn-border-top);border-left-color:var(--swmp-btn-border-left);filter:unset}button.swmp.swmp-button span{display:block;width:16px;height:16px;image-rendering:crisp-edges;image-rendering:pixelated;background-repeat:no-repeat;background-color:var(--swmp-button-mask-color);-webkit-mask-image:var(--image);mask-image:var(--image);-webkit-mask-size:16px;mask-size:16px;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}button.swmp.swmp-button:active span{transform:translateX(1px) translateY(1px)}span.swmp.swmp-window button{height:20px;min-width:22px;width:22px}button.swmp.swmp-button.swmp-playlist span{--image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAADFBMVEUAAAAAAAAAAAAAAAA16TeWAAAABHRSTlMARP/Eyy24XQAAAHxJREFUeJzN0MENgCAMBVBkHKdgHqdhCGLCBxMODsAIxomkLQca4h0OlBcaaGvMWsvRtvFxN5mCLQJ4ApwgMLwAlZA6bgIeQbtqSB0XoSUw4AixIxNQBImRBTh5hwC/UGkvP3PMT4+fqnLGQiO3EObmVNvjQNSoONgqWGt96g97WXTz3RYAAAAASUVORK5CYII=)}button.swmp.swmp-button.swmp-playlist.swmp-playlist-prev span{transform:rotate(180deg)}button.swmp.swmp-button.swmp-window-minimize span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAARElEQVRYR+3V0Q0AMAQFQPYfuv7apAPg4yzguQQZw5XD/UMAAgQIECCwTuA0fcc7+C8gAIFxgaYleG3W3QECBAgQaBcokVQGIRA6KiEAAAAASUVORK5CYII=');margin-top:2px;-webkit-mask-size:12px;mask-size:12px}button.swmp.swmp-button.swmp-window-maximize span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANUlEQVQ4T2NkoBAwQvX/J9McRqobADOQkINgLsZwwagBDAyjYUCFMCCUAtHlqZcXSLUZrh4AU/cYEVH9Rd0AAAAASUVORK5CYII=');margin-top:2px;-webkit-mask-size:12px;mask-size:12px}button.swmp.swmp-button.swmp-window-close span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA9UlEQVRoge2Y2woCMRBD5699dB79a7ViQfDWaZNMFybQR5NzVndhNatUKpUjxe/nctT+Vn59HsYItf+1nDFC7f9Ujhyh9v8qR4xQ+0fKV0bY/XYKDERHIvDtnGcEWBIyeIaEHB4pkQaPkEiHnwXxyc9QE/0mtrjyCgkZfA9SQg7fg5BIg+9ZkUiH75mR2Aa+xS0uwHyzC8Vt/ieULuG2fhOnSfgA3LYSDoBOk2DAyySi8O1RyXyzo8P3pEuswKdLIODTJJDwcgkGvFQiMhKBV/UPj0yXC/r/jiyXC/q/jsDKBf1vI/ByQf8jbtz/bdj9lUqlAs4N+1iFrUSwCpcAAAAASUVORK5CYII=');margin-top:2px;-webkit-mask-size:12px;mask-size:12px}button.swmp.swmp-button.swmp-playbutton span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA8klEQVRoQ+3aMQ6DMAyF4XDycjCGVmrPBRmapVLkJLafn1GRMiHB/+EpEVtJfm3J+8vtAO86kb2uI8tkfidwfsM/WSA9QBsAPUQC0ENGAbSQWQAdZBVAA9ECwiFWgDCINQAO8QLAIN4AdwgK4AZBA8whUQAzSDRADWEBLEPYANMQVsAwhB0gQrIAupA/AHR60d2bs09APFRgBYjhbfJsgOFwNsB0OAtgOTwaoA6PApiFowHm4SiAW7g3wD3cCwALtwbAw60AYeFaQHj4KoAmfBZAFz4KoA2XAPThPcCz3njU9QJtFdWvud2vBuovgn5A+glcJSF8MQrukbIAAAAASUVORK5CYII=')}button.swmp.swmp-button.swmp-playbutton.swmp-playing span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAtUlEQVRoQ+2Y2w6AIAxD5f8/2kuCRgiJbdjDMMfnxUE5o9WyLf6Uxde//W4D+8SJuGKE9OqbhrxUFCGkFxt4qQ1CInpX2SMWCIGQwc1ILBACIRBqFQixd1HUkF4MMUMs8taVESVGupFGDZpACIROBTCy5tOsMhGiijiMIb2IEkQJkTeihCAUUUIQ6S4hShAliBKVAZwYJzbuTn7ufoiFExs04cRpndg4xRyl7uDlWPWE+aTbwAFy3FQxPpmarQAAAABJRU5ErkJggg==')}button.swmp.swmp-button.swmp-stopbutton span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAUUlEQVRYR+3Xuw0AIAwD0WQC2H9LJuDXICFqm+KywEWvc4b50tyP7x7oIpE6O221bgEeQAABBBBAAAEEEEAAAQTsAkW0jPYqei0jUf9k7ON0AGFsNSFlb3+JAAAAAElFTkSuQmCC')}.swmp.swmp-timer-container,button.swmp.swmp-button.swmp-fullscreen{margin-left:auto}button.swmp.swmp-button.swmp-fullscreen span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAP0lEQVQ4T2NkoBAwQvX/x2EOQXmCCghZgG4AjE/IYzAXMw4jAwj5Gac8sYFG0AB4qBLplOEYCwOfF4gMfExlADQ3GBE+X9RsAAAAAElFTkSuQmCC')}button.swmp.swmp-button.swmp-volume span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAG1BMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABp4cHsAAAACHRSTlMA1WZ/JZz0Rme5O54AAACGSURBVDjL5ZIxCoAwDEWjVXDs6OjUWXDxRl6hqyL2H9uqKKL5oKtmzIOXNvkiP6qc9LOSAAciAoiIAAcdRNEGmrtoBQb1TbSCFt3eK3CUSAr0KliUlQbEA1YFCTCqID47qMAAwztAVXS4x2RfffC8kusSw7O100Px0/Iw0PjwwPGI0lB/s2bRbW7duVgj2wAAAABJRU5ErkJggg==')}button.swmp.swmp-button.swmp-volume.swmp-min span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAd0lEQVR42u2Xuw2AMAwFr41YhKmyVyp2YhZ6pwEJqJDgmRTv3PukV/gDxpjBKDRl+5mV0LWvbIRKUGjEXrJoZIIjGongHI1AcI3mc8E9mpeCeFwWWGCBBTqBfNj9MK4TFk7CykxZ+glnS8LhlXI6Akws/gCMGZAO8wpmVouK9vcAAAAASUVORK5CYII=')}button.swmp.swmp-button.swmp-volume.swmp-max span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEVHcEwAAAAAAAAAAAAAAADTrAj/AAAABXRSTlMA/4hD1KmiQHMAAACwSURBVDjL1ZPBDYMwDEV/nQzAIwwAVQdoNoD9l+qhgAyKW/VGfbHkp/wk9rf0//EI6sZ8Kqx5oj/U07IdgKcHlXfOcNAyVpCA4oU2oAqDF9pB9pcYK7hLAkYnBJKMUZqgc0IgaaJIFYp0Yw/JYJAy9CegBUZZA1SYlWA4gwxdExgUCWiA/mfQlgovj56bog/6loRN/Nz2w6CSG1Q82tgMoX1iw8UWDU0dr8HXxblwvAAGdxy1HX87LAAAAABJRU5ErkJggg==')}button.swmp.swmp-button.swmp-volume.swmp-mute span{--image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAHlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3KG9qAAAACnRSTlMACZko0v9e9AufnFf4OQAAAJdJREFUeJzlkrsNgDAMRCMhL8BO6WluBmZha2LZDvmdRA1uUHi+S/xJ6Uexk/9bJuAEMcIabBmQQNLknFBw2KERFyMFcEEFalROMAlCaUYFWKoLBDU81wUdkCobgGbHDT0Q/06gSOJJA3hqeKmgd7BX0TpY5V2v2ibO3bW2L+YRg5on6KOtRT9AzdIy2PrwheMrmi4GPhk3sCZY/9usUnQAAAAASUVORK5CYII=')}span.swmp.swmp-volume-container{display:flex;width:60px;height:100%;position:relative}span.swmp.swmp-volume-container input.swmp.swmp-volume.swmp-range{width:50px;vertical-align:top;position:relative}button.swmp.swmp-button:active{border-bottom-color:var(--swmp-btn-border-bottom-active);border-right-color:var(--swmp-btn-border-right-active);border-top-color:var(--swmp-btn-border-top-active);border-left-color:var(--swmp-btn-border-left-active)}span.swmp.swmp-timer-container{display:inline-table;margin-right:5px;cursor:default}span.swmp.swmp-timer-container span.swmp.swmp-time{display:table-cell;vertical-align:bottom}#quote-preview{z-index:1}div.swmp.swmp-theme-modernity,div.swmp.swmp-theme-modernity *{--swmp-background:#222;--swmp-container-border:#222;--swmp-text-color:#eee;--swmp-button-mask-color:#eee;--swmp-controls-background:#222;--swmp-btn-border-left:#0000;--swmp-btn-border-top:#0000;--swmp-btn-border-right:#0000;--swmp-btn-border-bottom:#0000;--swmp-btn-border-left-active:#0000;--swmp-btn-border-top-active:#0000;--swmp-btn-border-right-active:#0000;--swmp-btn-border-bottom-active:#0000;--swmp-seek-background:#444;--swmp-seek-progress-color:#bbb;--swmp-seek-border-left:#444;--swmp-seek-border-top:#444;--swmp-seek-border-right:#444;--swmp-seek-border-bottom:#444;--swmp-container-box-shadow:0px 0px 10px #000;font-family:Arial;font-weight:700}div.swmp.swmp-theme-modernity input[type=range]::-moz-range-thumb{border:none;height:8px;width:8px;background:#eee;box-shadow:none;border-radius:100%;padding:4px}div.swmp.swmp-theme-modernity input[type=range]::-webkit-slider-thumb{border:none;height:8px;width:8px;background:#eee;box-shadow:none;border-radius:100%;padding:8px}div.swmp.swmp-theme-winxp,div.swmp.swmp-theme-winxp *{--swmp-background:#0253d2;--swmp-container-border:none;--swmp-container-border-radius:3px;--swmp-container-box-shadow:#00000052 0px 0px 1px 0px;--swmp-controls-background:#ece9d8;--swmp-button-background:#fff;--swmp-seek-background:#ddd;--swmp-seek-progress-color:#17b900;--swmp-seek-border-left:#a4a4a4;--swmp-seek-border-top:#a4a4a4;--swmp-seek-border-right:#fff;--swmp-seek-border-bottom:#fff;--swmp-range-thumb-color:#fff;--swmp-range-thumb-border-left:#fff;--swmp-range-thumb-border-top:#3319e4;--swmp-btn-border-radius:3px;--swmp-btn-border-left:#2202ff;--swmp-btn-border-top:#2202ff;--swmp-btn-border-right:#000;--swmp-btn-border-bottom:#000;--swmp-btn-border-left-active:#000;--swmp-btn-border-top-active:#000;--swmp-btn-border-right-active:#000;--swmp-btn-border-bottom-active:#000}div.swmp.swmp-theme-winxp .swmp-window-title{color:#fff;text-shadow:1px 1px #0f1089}div.swmp.swmp-theme-winxp .swmp.swmp-window.swmp-window-container{background:linear-gradient(180deg,#0684df,#0347c6 8%,#0240bb 40%,#0451c6 88%,#0253cc 93%,#0149cc 95%,#0230a4 100%)}div.swmp.swmp-theme-winxp button.swmp.swmp-button:hover{box-shadow:inset 0 0 2px 0 #ce700b}div.swmp.swmp-theme-winxp button.swmp.swmp-button:active{box-shadow:inset 0 0 2px 0 #074faa}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button{background:linear-gradient(#1690e8,#2969e3 8%,#0438bd 100%);box-shadow:none!important;border-color:#fff}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button:hover{background:linear-gradient(#2295e8,#3674ec 8%,#0b3fc6 100%)}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button:active{background:linear-gradient(#0c74bf,#144cb7 8%,#0438bd 100%)}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button span{background-color:#fff}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button.swmp-window-close{background:linear-gradient(#ea4646,#e65a5a 8%,#d22828 100%)}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button.swmp-window-close:hover{background:linear-gradient(#ea5050,#e66a6a 8%,#db3434 100%)}div.swmp.swmp-theme-winxp .swmp-window button.swmp-button.swmp-window-close:active{background:linear-gradient(#d22b2b,#c33838 8%,#d22828 100%)}div.swmp.swmp-theme-winxp span.swmp input[type=range]::-moz-range-thumb{border:1px solid #000;border-radius:2px 2px 4px 4px!important;height:16px;width:10px;box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 #4d85dd;background:linear-gradient(180deg,#a7c8d9 20%,#fff 20%,#fff 80%,#a7c8d9 80%)}div.swmp.swmp-theme-winxp span.swmp input[type=range]::-webkit-slider-thumb{border:1px solid #000;border-radius:2px 2px 4px 4px!important;height:18px;width:12px;box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 #4d85dd;background:linear-gradient(180deg,#a7c8d9 20%,#fff 20%,#fff 80%,#a7c8d9 80%)}div.swmp.swmp-theme-winxp span.swmp input.swmp-volume[type=range]::-moz-range-thumb{width:6px}div.swmp.swmp-theme-winxp span.swmp input.swmp-volume[type=range]::-webkit-slider-thumb{width:10px}div.swmp.swmp-theme-winxp span.swmp input:hover[type=range]::-moz-range-thumb{background:linear-gradient(180deg,#d9cca7 20%,#fff 20%,#fff 80%,#d9cca7 80%);box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 #e9967a}div.swmp.swmp-theme-winxp span.swmp input:hover[type=range]::-webkit-slider-thumb{background:linear-gradient(180deg,#d9cca7 20%,#fff 20%,#fff 80%,#d9cca7 80%);box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 #e9967a}div.swmp.swmp-theme-winxp span.swmp input:active[type=range]::-moz-range-thumb{box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 orange;background:linear-gradient(180deg,orange 20%,#fff 20%,#fff 80%,#ff8c00 80%)}div.swmp.swmp-theme-winxp span.swmp input:active[type=range]::-webkit-slider-thumb{box-shadow:inset 1px 1px 0 0 #fff,inset -1px -1px 0 0 orange;background:linear-gradient(180deg,orange 20%,#fff 20%,#fff 80%,#ff8c00 80%)}@media screen and (max-width:480px){div.swmp.swmp-container{top:5px!important;right:5px!important;left:5px!important;margin-bottom:10px}div.swmp.swmp-container.swmp-maximized{margin:0}div.swmp audio,div.swmp iframe,div.swmp video{max-width:100%!important;max-height:100%!important;min-width:unset;min-height:unset;width:100%;height:100%}span.swmp.swmp-volume-container{display:none}}`;
  210. document.head.appendChild(swmpStyle);
  211. }
  212.  
  213. var youtubeIsLoaded = false;
  214. var gmwindow;
  215. if (typeof GM_info != 'undefined') {
  216. gmwindow = unsafeWindow;
  217. } else {
  218. gmwindow = window;
  219. }
  220.  
  221. var fileregex = new RegExp(`\.(${swmpConfig.files})+$`, 'gmi');
  222. var ytregex = new RegExp("^(?:https?:)?//[^/]*(?:youtube(?:-nocookie)?\.com|youtu\.be|yewtu\.be).*[=/]([-\\w]{11})(?:\\?|=|&|$)", "gmi");
  223.  
  224. // SWMP Code
  225.  
  226. class swmp {
  227.  
  228. constructor(obj) {
  229. this.name = 'Simple Web Media Player';
  230.  
  231. if (obj.id == undefined) {
  232. this.id = this.uuid();
  233. } else {
  234. this.id = obj.id;
  235. }
  236.  
  237. this.type = obj.type; // "video" or "audio" player. Youtube too.
  238. this.mime = obj.mime; // MIME type for source, Example: "video/webm". Feed to enable checking for file support (webm not supported by iOS)
  239. this.url = obj.url; // File Location
  240. this.poster = obj.poster; // Optional Video Preview for when autoplay is off.
  241. this.autoplay = obj.autoplay; // Optional Autoplay
  242. this.loop = obj.loop; // Optional Loop
  243. this.windowed = obj.windowed; // Optional set false to disable windowed mode and place inline
  244.  
  245. this.defaultVolume = parseInt(swmpConfig.volume);
  246.  
  247. this.playlistLocation = 0;
  248. this.playlist = obj.playlist;
  249.  
  250. if (obj.url == undefined) {
  251. console.log('No media given');
  252. return false;
  253. }
  254.  
  255. if (obj.windowed == undefined) {
  256. this.windowed = swmpConfig.windowed;
  257. }
  258.  
  259. if (obj.title == undefined) {
  260. this.title = this.getFileName(obj.url);
  261. } else {
  262. this.title = obj.title; // If Applicable, assign from existing parameters on IB.
  263. }
  264.  
  265. // Check URL for Type and Mime, also sets mime+type based on fileextension in url
  266. if (this.checkURL(this.url) == false) {
  267. return false;
  268. }
  269.  
  270. // Create Container
  271. this.container = document.createElement('div');
  272. this.container.setAttribute('class', 'swmp swmp-container');
  273. this.container.setAttribute('id', this.id);
  274. this.container.setAttribute('tabindex', '0');
  275.  
  276. // Create Window Container
  277. this.prepareWindow();
  278.  
  279. // Prepare the media that's gonna play.
  280.  
  281. if (this.type == 'video' || this.type == 'audio') {
  282. // Check if browser can play formats, create audio or video tag and fill with source.
  283. this.preparePlayer();
  284. } else if (this.type == 'youtube') {
  285. // Load Youtube iframe.
  286. if (this.prepareYoutube() == false) {
  287. console.log("Error: YouTube");
  288. return false;
  289. }
  290. }
  291.  
  292. // Add Theme
  293. if (swmpConfig.theme != undefined) {
  294. this.container.classList.add(`swmp-theme-${swmpConfig.theme}`);
  295. }
  296.  
  297. if (this.type == 'video' || this.type == 'audio') {
  298. // Create buttons
  299. //this.prepareSharedEventsInit();
  300. this.prepareSharedEvents();
  301. this.prepareControls();
  302. this.preparePlayerEvents();
  303. this.prepareSettings();
  304. } else if (this.type == 'youtube') {
  305. //this.prepareSharedEvents();
  306. //this.prepareYoutubeEvents();
  307. }
  308.  
  309. this.prepareSharedEventsInit();
  310.  
  311. } //END CONSTRUCTOR
  312.  
  313. nextMedia() {
  314. if (this.playlistLocation === this.playlist.length -1) {
  315. return;
  316. }
  317.  
  318. this.playlistLocation++;
  319. this.url = this.playlist[this.playlistLocation];
  320. this.windowTitle.textContent = `[${this.playlistLocation+1}/${this.playlist.length}] ${this.url}`;
  321.  
  322. this.refreshPlayer();
  323. }
  324.  
  325. previousMedia() {
  326. if (this.playlistLocation === 0) {
  327. return;
  328. }
  329.  
  330. this.playlistLocation--;
  331. this.url = this.playlist[this.playlistLocation];
  332. this.windowTitle.textContent = `[${this.playlistLocation+1}/${this.playlist.length}] ${this.url}`;
  333.  
  334. this.refreshPlayer();
  335. }
  336.  
  337. clearPlayer() {
  338.  
  339. if (this.player.interval != undefined) {
  340. clearInterval(this.player.interval);
  341. }
  342.  
  343. if (this.closeError != undefined) {
  344. this.closeError.remove();
  345. }
  346.  
  347. /*if (timeInterval != undefined) {
  348. clearInterval(timeInterval); //the yt timer is completely borken
  349. }*/
  350.  
  351. this.container.classList.remove('swmp-video');
  352. this.container.classList.remove('swmp-audio');
  353. this.container.classList.remove('swmp-youtube');
  354.  
  355. if (this.settingsContainer != undefined) {
  356. this.settingsContainer.innerHTML = null;
  357. this.settingsContainer.remove();
  358. }
  359. this.controls.innerHTML = null;
  360. this.controls.remove();
  361. this.playerContainer.innerHTML = null;
  362. this.playerContainer.remove();
  363. this.player.innerHTML = null;
  364. this.player.remove();
  365.  
  366.  
  367. }
  368.  
  369. refreshPlayer() {
  370. this.clearPlayer();
  371.  
  372. this.checkURL(this.url);
  373.  
  374. if (this.type == 'youtube') {
  375. this.prepareYoutube();
  376. }
  377.  
  378. if (this.type == 'video' || this.type == 'audio') {
  379. this.preparePlayer();
  380. this.prepareSharedEvents();
  381. this.prepareControls();
  382. this.preparePlayerEvents();
  383. this.prepareSettings();
  384. }
  385. }
  386.  
  387. prepareWindow() {
  388. this.windowContainer = document.createElement('div');
  389. this.windowContainer.setAttribute('class', 'swmp swmp-window swmp-window-container');
  390. this.container.appendChild(this.windowContainer);
  391.  
  392. // Create Title/WindowDragbar and put inside Window Container
  393. this.windowTitlebar = document.createElement('span');
  394. this.windowTitlebar.setAttribute('class', 'swmp swmp-window swmp-window-titlebar');
  395.  
  396. this.windowTitle = document.createElement('span');
  397. this.windowTitle.setAttribute('class', 'swmp swmp-window swmp-window-title');
  398.  
  399. if (this.playlist != undefined) {
  400. if (this.title != undefined) {
  401. this.windowTitle.textContent = `[${this.playlistLocation+1}/${this.playlist.length}] ${this.title}`;
  402. } else {
  403. this.windowTitle.textContent = `[${this.playlistLocation+1}/${this.playlist.length}] ${this.url}`;
  404. }
  405. } else {
  406. if (this.title != undefined) {
  407. this.windowTitle.textContent = this.title;
  408. } else {
  409. this.windowTitle.textContent = this.url;
  410. }
  411. }
  412.  
  413. this.windowTitlebar.appendChild(this.windowTitle);
  414. this.windowContainer.appendChild(this.windowTitlebar);
  415.  
  416. // Create Window Buttons Container
  417. this.windowButtonsContain = document.createElement('span');
  418. this.windowButtonsContain.setAttribute('class', 'swmp swmp-window swmp-window-buttons-contain');
  419. this.windowContainer.appendChild(this.windowButtonsContain);
  420.  
  421. // Create Minimize Button (Video only)
  422. if (this.type == 'video' || this.type == 'youtube') {
  423. this.windowMinimize = document.createElement('button');
  424. this.windowMinimize.setAttribute('class', 'swmp swmp-button swmp-window-minimize');
  425. this.windowMinimize.innerHTML = '<span></span>';
  426. this.windowMinimize.addEventListener('click', (event) => {
  427. event.preventDefault();
  428. if (this.container.classList.contains('swmp-minimized') ) {
  429. this.container.classList.remove('swmp-minimized');
  430. } else {
  431. this.container.classList.add('swmp-minimized');
  432. }
  433. });
  434. this.windowButtonsContain.appendChild(this.windowMinimize);
  435. }
  436.  
  437. // Create Maximize Button (Video only)
  438. if (this.type == 'video' || this.type == 'youtube') {
  439. this.windowMaximize = document.createElement('button');
  440. this.windowMaximize.setAttribute('class', 'swmp swmp-button swmp-window-maximize');
  441. this.windowMaximize.innerHTML = '<span></span>';
  442. this.windowMaximize.addEventListener('click', this.toggleMaximize);
  443. this.windowButtonsContain.appendChild(this.windowMaximize);
  444. }
  445.  
  446. // Create Close Button
  447. this.windowClose = document.createElement('button');
  448. this.windowClose.setAttribute('class', 'swmp swmp-button swmp-window-close');
  449. this.windowClose.innerHTML = '<span></span>';
  450. this.windowClose.addEventListener('click', (event) => {
  451. event.preventDefault();
  452. this.container.remove();
  453. });
  454. this.windowButtonsContain.appendChild(this.windowClose);
  455.  
  456. // Window Event
  457. if (swmpConfig.windowed != 'false') {
  458. this.makeDraggable(this.container);
  459. } else {
  460. this.windowTitlebar.style.cursor = 'default';
  461. }
  462.  
  463.  
  464. // Disable Default Context Menu on Titlebar
  465. this.windowTitlebar.addEventListener('contextmenu', function(evt) {
  466. evt.preventDefault();
  467. }, false);
  468. // Right Click Event
  469. this.windowTitlebar.addEventListener('mousedown', (event) => {
  470. event.preventDefault();
  471. switch (event.which) {
  472. case 3: //rightclick
  473. this.openSettings();
  474. break;
  475. }
  476. });
  477.  
  478. // Maximize
  479. this.windowTitlebar.addEventListener('dblclick', this.toggleMaximize);
  480.  
  481. }
  482.  
  483. prepareControls() {
  484. // Create and put Controls inside Container
  485. this.controls = document.createElement('div');
  486. this.controls.setAttribute('class', 'swmp swmp-controls');
  487. this.container.appendChild(this.controls);
  488.  
  489. // Disable Default Context Menu on Controls
  490. this.controls.addEventListener('contextmenu', function(evt) {
  491. evt.preventDefault();
  492. }, false);
  493. // Right Click Event
  494. this.controls.addEventListener('mousedown', (event) => {
  495. switch (event.which) {
  496. case 3: //rightclick
  497. this.openSettings();
  498. break;
  499. }
  500. });
  501.  
  502. // Create Progress/Seeker Container
  503. this.seekContain = document.createElement('span');
  504. this.seekContain.setAttribute('class', 'swmp swmp-seek-container');
  505. this.controls.appendChild(this.seekContain);
  506.  
  507. // Create Progress Bar
  508. this.progress = document.createElement('progress');
  509. this.progress.setAttribute('value', '0');
  510. this.progress.setAttribute('min', '0');
  511. this.progress.setAttribute('max', '1000');
  512. this.progress.setAttribute('step', '1');
  513. this.progress.setAttribute('class', 'swmp swmp-progress');
  514. this.seekContain.appendChild(this.progress);
  515.  
  516. // Create Seeker Input
  517. this.seeker = document.createElement('input');
  518. this.seeker.setAttribute('type', 'range');
  519. this.seeker.setAttribute('value', '0');
  520. this.seeker.setAttribute('min', '0');
  521. this.seeker.setAttribute('max', '1000');
  522. this.seeker.setAttribute('step', '1');
  523. this.seeker.setAttribute('class', 'swmp swmp-seeker');
  524. this.seekContain.appendChild(this.seeker);
  525.  
  526. // Create a Row Bottom Container
  527. this.bottomRow = document.createElement('span');
  528. this.bottomRow.setAttribute('class', 'swmp swmp-row-bottom');
  529. this.controls.appendChild(this.bottomRow);
  530.  
  531. // Create Buttons Container
  532. this.buttonsContain = document.createElement('span');
  533. this.buttonsContain.setAttribute('class', 'swmp swmp-buttons-container');
  534. this.bottomRow.appendChild(this.buttonsContain);
  535.  
  536. // Create Play/Pause Button and put inside Controls
  537. this.playbutton = document.createElement('button');
  538. this.playbutton.setAttribute('class', 'swmp swmp-button swmp-playbutton');
  539. this.playbutton.innerHTML = "<span></span>"; // ◀
  540. this.playbutton.addEventListener("click", event => {
  541. event.preventDefault();
  542.  
  543. if (this.type == 'video' || this.type == 'audio') {
  544. if (this.player.paused ) {
  545. this.player.play();
  546. } else {
  547. this.player.pause();
  548. }
  549. } else if (this.type == 'youtube') {
  550. this.togglePlay();
  551. }
  552. });
  553. this.buttonsContain.appendChild(this.playbutton);
  554.  
  555. // Create a Stop/Reload Button and put inside Controls
  556. this.stopbutton = document.createElement('button');
  557. this.stopbutton.setAttribute('class', 'swmp swmp-button swmp-stopbutton');
  558. this.stopbutton.innerHTML = "<span></span>"; // ■
  559. this.stopbutton.addEventListener("click", event => {
  560. event.preventDefault();
  561. if (this.loaded != true) {
  562. console.log('Userscript: Can\'t send stop yet.');
  563. return;
  564. }
  565.  
  566. if (this.type == 'video' || this.type == 'audio') {
  567. this.player.pause();
  568. this.seeker.value = 0;
  569. this.seeker.setAttribute("value", 0);
  570. this.progress.value = 0;
  571. this.progress.setAttribute("value", 0);
  572. this.player.currentTime = 0;
  573. this.currentTimer.textContent = '00:00';
  574. } else if (this.type == 'youtube') {
  575. this.seeker.value = 0;
  576. this.seeker.setAttribute("value", 0);
  577. this.progress.value = 0;
  578. this.progress.setAttribute("value", 0);
  579. this.ytplayer.stopVideo();
  580. this.currentTimer.textContent = '00:00';
  581. }
  582. });
  583. this.buttonsContain.appendChild(this.stopbutton);
  584.  
  585. if (this.playlist) {
  586. this.previousButton = document.createElement('button');
  587. this.previousButton.setAttribute('class', 'swmp swmp-button swmp-playlist swmp-playlist-prev');
  588. this.previousButton.innerHTML = "<span></span>";
  589. this.previousButton.addEventListener('click', event => {
  590. event.preventDefault();
  591. this.previousMedia();
  592. });
  593. this.buttonsContain.appendChild(this.previousButton);
  594.  
  595. this.nextButton = document.createElement('button');
  596. this.nextButton.setAttribute('class', 'swmp swmp-button swmp-playlist');
  597. this.nextButton.innerHTML = "<span></span>";
  598. this.nextButton.addEventListener('click', event => {
  599. event.preventDefault();
  600. this.nextMedia();
  601. });
  602. this.buttonsContain.appendChild(this.nextButton);
  603. }
  604.  
  605. // Create a Volume Container
  606. this.volumeContain = document.createElement('span');
  607. this.volumeContain.setAttribute('class', 'swmp swmp-volume-container');
  608. this.bottomRow.appendChild(this.volumeContain);
  609.  
  610. // Create a Volume Button and put inside Buttons Container
  611. this.volumeButton = document.createElement('button');
  612. this.volumeButton.setAttribute('class', 'swmp swmp-button swmp-volume');
  613. this.volumeButton.innerHTML = "<span></span>";
  614. this.volumeButton.addEventListener("click", event => {
  615. event.preventDefault();
  616. this.toggleMute();
  617. });
  618. this.buttonsContain.appendChild(this.volumeButton);
  619.  
  620. // Create a Volume Progress and put inside Volume Container
  621. this.volumeProgress = document.createElement('progress');
  622. this.volumeProgress.setAttribute('value', this.defaultVolume);
  623. this.volumeProgress.setAttribute('min', '0');
  624. this.volumeProgress.setAttribute('max', '100');
  625. this.volumeProgress.setAttribute('step', '1');
  626. this.volumeProgress.setAttribute('class', 'swmp swmp-volume swmp-progress');
  627. this.volumeContain.appendChild(this.volumeProgress);
  628.  
  629. // Create a Volume Input and put inside Volume Container
  630. this.volumeRange = document.createElement('input');
  631. this.volumeRange.setAttribute('type', 'range');
  632. this.volumeRange.setAttribute('value', this.defaultVolume);
  633. this.volumeRange.setAttribute('min', '0');
  634. this.volumeRange.setAttribute('max', '100');
  635. this.volumeRange.setAttribute('step', '1');
  636. this.volumeRange.setAttribute('class', 'swmp swmp-volume swmp-range');
  637. this.volumeContain.appendChild(this.volumeRange);
  638.  
  639. // Create a Timer Container and put inside Controls
  640. this.timerContain = document.createElement('span');
  641. this.timerContain.setAttribute('class', 'swmp swmp-timer-container');
  642. this.bottomRow.appendChild(this.timerContain);
  643.  
  644. // Create a timerCurrent and put inside Timer Container
  645. this.currentTimer = document.createElement('span');
  646. this.currentTimer.setAttribute('class', 'swmp swmp-time swmp-current');
  647. this.currentTimer.textContent = '00:00';
  648. this.timerContain.appendChild(this.currentTimer);
  649.  
  650. // Add a separator between timer
  651. this.timerSeperator = document.createElement('span');
  652. this.timerSeperator.setAttribute('class', 'swmp swmp-time swmp-separator');
  653. this.timerSeperator.textContent = '/';
  654. this.timerContain.appendChild(this.timerSeperator);
  655.  
  656. // Create a totalTimer and put inside Timer Container
  657. this.totalTimer = document.createElement('span');
  658. this.totalTimer.setAttribute('class', 'swmp swmp-time swmp-total');
  659. this.totalTimer.textContent = '00:00';
  660. this.timerContain.appendChild(this.totalTimer);
  661.  
  662. // Create a Fullscreen Button and put inside Bottom Row Container
  663. if (this.type == 'video' || this.type == 'youtube' || (this.type == 'audio' && this.playlist != null) ) {
  664. this.fullscreenbutton = document.createElement('button');
  665. this.fullscreenbutton.setAttribute('class', 'swmp swmp-button swmp-fullscreen');
  666. this.fullscreenbutton.innerHTML = "<span></span>"; // ▣
  667. this.fullscreenbutton.addEventListener("click", event => {
  668. event.preventDefault();
  669. this.toggleFullscreen();
  670. });
  671. this.bottomRow.appendChild(this.fullscreenbutton);
  672. }
  673. }
  674.  
  675. prepareSettings() {
  676.  
  677. this.openSettings = () => {
  678. // Settings Menu
  679.  
  680. if (this.container.querySelector('.swmp-settings-container') != null) {
  681. this.settingsContainer.remove();
  682. return false; //Already open
  683. }
  684.  
  685. this.settingsContainer = document.createElement('div');
  686. this.settingsContainer.setAttribute('class', 'swmp swmp-settings swmp-settings-container');
  687. this.settingsContainer.innerHTML = 'Settings:';
  688.  
  689.  
  690. this.br = document.createElement('br');
  691. this.settingsContainer.appendChild(this.br);
  692.  
  693. this.themeSelector = document.createElement('select');
  694. this.themeSelector.setAttribute('class', 'swmp swmp-selector swmp-settings swmp-theme-selector');
  695.  
  696. this.themes = '';
  697. swmpConfig.themes.forEach(theme => {
  698. if (localStorage.swmpTheme == theme[0]) {
  699. this.themeSelector.value = theme[0];
  700. this.themes += `<option value="${theme[0]}" selected>${theme[1]}</option>`;
  701. } else {
  702. this.themes += `<option value="${theme[0]}">${theme[1]}</option>`;
  703. }
  704. });
  705. this.themeSelector.innerHTML = this.themes;
  706.  
  707. this.themeSelector.onchange = (event) => {
  708. if (this.themeSelector.value == '') {
  709. return false;
  710. } else {
  711. this.removeClassByPrefix(this.container, 'swmp-theme-'); //regex remove [theme-*]
  712. this.container.classList.add(`swmp-theme-${this.themeSelector.value}`);
  713. swmpConfig.theme = this.themeSelector.value;
  714. localStorage.swmpTheme = this.themeSelector.value;
  715. }
  716. }
  717.  
  718. this.settingsContainer.appendChild(this.themeSelector);
  719.  
  720. // Defines true/false checkboxes.
  721. this.settingsCheckboxContent = {
  722. autoplay: {
  723. 'label': 'Autoplay',
  724. 'desc': 'Enable/Disable Autoplaying of media.'
  725. },
  726. loop: {
  727. 'label': 'Loop',
  728. 'desc': 'Enable/Disable Looping of media.'
  729. },
  730. allowMultiple: {
  731. 'label': 'Multi',
  732. 'desc': 'Disabled will remove existing players and open new.'
  733. },
  734. volumeScroll: {
  735. 'label': 'Volume Scrolling',
  736. 'desc': 'Enabled will allow using scroll wheel on player to change volume.'
  737. },
  738. downloadAttribute: {
  739. 'label': 'Override Download',
  740. 'desc': 'Enable to make [download=""] links launch player instead.'
  741. },
  742. doubleclickMaximize: {
  743. 'label': 'Doubleclick Maximize',
  744. 'desc': 'When enabled replaces the default behavior from doubleclick fullscreen on video to instead maximize the video size.'
  745. },
  746. windowed: {
  747. 'label': 'Movable Window',
  748. 'desc': 'When Enabled opens the player in a fixed movable window. Disable to inline.'
  749. }
  750. }
  751. this.checkbox = [];
  752. this.createToggle = function(key, obj) {
  753. // Label
  754. this.checkbox[`${key}Label`] = document.createElement('label');
  755. this.checkbox[`${key}Label`].setAttribute('class', `swmp swmp-settings swmp-label swmp-${key}-label`);
  756. this.checkbox[`${key}Label`].textContent = `${obj['label']}`;
  757. this.checkbox[`${key}Label`].setAttribute('title', `${obj['desc']}`);
  758. // Checkbox
  759. this.checkbox[`${key}Check`] = document.createElement('input');
  760. this.checkbox[`${key}Check`].setAttribute('class', `swmp swmp-settings swmp-input swmp-${key}-input`);
  761. this.checkbox[`${key}Check`].setAttribute('type', 'checkbox');
  762. // Initialize value
  763. if (localStorage[`swmp${upperFirst(key)}`] == 'true') {
  764. this.checkbox[`${key}Check`].setAttribute('checked', 'checked');
  765. }
  766. // Event listener for true/false
  767. this.checkbox[`${key}Check`].addEventListener('change', (event) => {
  768. if (this.checkbox[`${key}Check`].checked == true) {
  769. //SPECIFIC ACTIONS
  770. //LOOP
  771. if (key == 'loop') {
  772. if (this.type == 'video' || this.type == 'audio') {
  773. this.player.setAttribute('loop', 'true');
  774. } else if (this.type == 'youtube') {
  775. // Youtube Loop
  776. }
  777. }
  778. //END SPECIFIC
  779. this.checkbox[`${key}Check`].setAttribute('checked', 'checked');
  780. localStorage.setItem(`swmp${upperFirst(key)}`, 'true');
  781. swmpConfig[`${key}`] = 'true';
  782. } else {
  783. //SPECIFIC ACTIONS
  784. //LOOP
  785. if (key == 'loop') {
  786. if (this.type == 'video' || this.type == 'audio') {
  787. this.player.removeAttribute('loop');
  788. } else if (this.type == 'youtube') {
  789. // Youtube Loop
  790. }
  791. }
  792. //END SPECIFIC
  793.  
  794. this.checkbox[`${key}Check`].removeAttribute('checked');
  795. localStorage.setItem(`swmp${upperFirst(key)}`, 'false');
  796. swmpConfig[`${key}`] = 'false';
  797. }
  798. });
  799.  
  800. // Put Checkbox inside Label
  801. this.checkbox[`${key}Label`].appendChild(this.checkbox[`${key}Check`]);
  802. // Add to settings container
  803. this.settingsContainer.appendChild(this.checkbox[`${key}Label`]);
  804. }
  805.  
  806. // Create the toggles
  807. Object.entries(this.settingsCheckboxContent).forEach( ([item, value]) => {
  808. this.createToggle(item, value);
  809. });
  810.  
  811. this.container.appendChild(this.settingsContainer);
  812.  
  813. }
  814. }
  815.  
  816. preparePlayer() {
  817. // Create Player HTML5 Video or Audio format.
  818. if (this.type == 'video') {
  819. this.container.classList.add('swmp-video');
  820. this.player = document.createElement('video');
  821. this.player.setAttribute('class', 'swmp swmp-video swmp-player');
  822. if (this.poster != false && this.poster != undefined) {
  823. this.player.setAttribute('poster', this.poster);
  824. }
  825. } else if (this.type == 'audio') {
  826. this.container.classList.add('swmp-audio');
  827. this.player = document.createElement('audio');
  828. this.player.setAttribute('class', 'swmp swmp-audio swmp-player');
  829. } else {
  830. console.log(`SWMP Error: invalid type of ${this.type}.`);
  831. return false;
  832. }
  833.  
  834. // Check Format Support
  835. if (this.mime != undefined) {
  836. if (this.player.canPlayType(this.mime) == '') {
  837. this.closeError = document.createElement('span');
  838. this.closeError.innerHTML = `Your browser can't play this format: ${this.mime}`;
  839. /*this.container.addEventListener('click', (event) => {
  840. this.container.remove();
  841. });*/
  842. this.container.appendChild(this.closeError);
  843. //return false;
  844. }
  845. }
  846.  
  847. // Preload metadata
  848. this.player.setAttribute('preload', 'metadata');
  849.  
  850. // Create Player Container and Put inside Container
  851. this.playerContainer = document.createElement('div');
  852. this.playerContainer.setAttribute('class', 'swmp swmp-player-container');
  853. this.container.appendChild(this.playerContainer);
  854.  
  855. // Put Player inside Video Container
  856. this.playerContainer.appendChild(this.player);
  857.  
  858. // Create and put Source inside Player
  859. this.source = document.createElement('source');
  860. this.source.setAttribute('src', this.url);
  861. if (this.mime != undefined) {
  862. this.source.setAttribute('type', this.mime);
  863. }
  864. this.player.appendChild(this.source);
  865.  
  866. // Is Autoplay?
  867. if ( (swmpConfig.autoplay == true || swmpConfig.autoplay == 'true') && this.autoplay != false) {
  868. this.player.setAttribute('autoplay', true);
  869. }
  870.  
  871. // Is Loop?
  872. if ( (swmpConfig.loop == true || swmpConfig.loop == 'true') && this.loop != false) {
  873. this.player.setAttribute('loop', true);
  874. }
  875. }
  876.  
  877. prepareYoutube() {
  878.  
  879. console.log('SWMP: prepareYoutube() ');
  880.  
  881. if (youtubeIsLoaded == false) {
  882. // Include YouTube iframe API if not already added:
  883. if (!document.getElementById('swmp-youtube-api')) {
  884. console.log("SWMP: Loading YouTube API");
  885. var tag = document.createElement('script');
  886. tag.src = "https://www.youtube.com/iframe_api";
  887. tag.setAttribute('id', 'swmp-youtube-api');
  888. var firstScriptTag = document.getElementsByTagName('script')[0];
  889. firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  890. }
  891.  
  892. gmwindow.onYouTubeIframeAPIReady = (event) => {
  893. youtubeIsLoaded = true;
  894. console.log('SWMP: Firing YouTube Ready Event from inside PrepareYouTube()');
  895. this.makeYoutube();
  896. }
  897.  
  898.  
  899. } else {
  900. // API Already loaded, just make video.
  901. console.log('SWMP: YT API Already loaded, make vid');
  902. this.makeYoutube();
  903. }
  904. }
  905.  
  906. makeYoutube() {
  907. // Make new player
  908. console.log('SWMP: makeYoutube()');
  909. this.playerfirstrun = true;
  910. var self = this;
  911. this.loaded = false;
  912. this.container.classList.add('swmp-youtube');
  913. this.onPlayerReady = (event) => {
  914. this.loaded = true;
  915. if (!this.container.classList.contains('swmp-youtube')) { //cancel if already changed playlist
  916. return;
  917. }
  918. console.log('SWMP Youtube: onPlayerReady()');
  919. if (this.playerfirstrun == true) {
  920. this.playerfirstrun = false;
  921. this.playerContainer.appendChild(this.ytplayer.getIframe() );
  922. if (this.playlist) {
  923. this.windowTitle.textContent = `[${this.playlistLocation+1}/${this.playlist.length}] ${this.ytplayer.getVideoData().title}`;
  924. } else {
  925. this.windowTitle.textContent = this.ytplayer.getVideoData().title;
  926. }
  927. this.totalTimer.textContent = this.formatSeconds(this.ytplayer.getDuration() );
  928. this.ytplayer.getIframe().style.display = 'inherit';
  929.  
  930. this.ytplayer.setVolume(this.defaultVolume);
  931. this._volume = this.defaultVolume;
  932. /*if (swmpConfig.muted == 'true') {
  933. this.ytplayer.mute();
  934. this.volumeButton.classList.add('swmp-mute');
  935. } else {
  936. this.ytplayer.unMute();
  937. }*/
  938. this.ytplayer.unMute();
  939.  
  940. if (this.autoplay == 'true') {
  941. this.ytplayer.playVideo();
  942. this.playbutton.classList.add('swmp-playing');
  943. }
  944.  
  945.  
  946. }
  947.  
  948. window.addEventListener('message', function(event) {
  949. if (event.source === self.ytplayer.getIframe().contentWindow) {
  950. var data = JSON.parse(event.data);
  951. //console.log(data);
  952.  
  953. // Dispatch Volume and Mute Event -- These are sent together by iframe.
  954. if (data.event === "infoDelivery" && data.info && data.info.volume) {
  955. //console.log(data.info.volume);
  956. //console.log(data.info.muted);
  957. if (data.info.volume != self._volume) {
  958. //console.log(data.info.volume);
  959. self.updateVolume();
  960. }
  961. }
  962.  
  963. // Dispatch on Time Event
  964. if (data.event === "infoDelivery" && data.info && data.info.currentTime) {
  965. //console.log(data.info.currentTime);
  966. self.currentTimer.textContent = self.formatSeconds(data.info.currentTime);
  967. }
  968.  
  969. // Dispatch on Player state event
  970. if (data.event === "infoDelivery" && data.info && data.info.playerState) {
  971. //console.log(data.info.playerState);
  972. self.updatePlay();
  973. self.updateVolume();
  974. }
  975. }
  976. });
  977.  
  978. }
  979.  
  980. this.videogenerate = document.createElement('div');
  981. this.videogenerate.style.display = 'none';
  982. this.videogenerate.setAttribute('id', `ytplayer-${this.id}`);
  983. document.body.appendChild(this.videogenerate);
  984.  
  985. this.youtubeautoplay = (swmpConfig.autoplay=='true')?1:0;
  986. this.youtubeloop = (swmpConfig.loop=='true')?1:0;
  987.  
  988. this.ytplayer = new gmwindow.YT.Player(`ytplayer-${this.id}`, {
  989. height: '1080',
  990. width: '1920', // Allows quality to increase to 1080p. Can't be forced by API, need fast internet.
  991. videoId: this.videoid,
  992. playerVars: {
  993. 'playsinline': 1,
  994. 'autoplay': this.youtubeautoplay,
  995. 'loop': this.youtubeloop,
  996. //'origin': window.location.href,
  997. 'rel': 0,
  998. 'modestbranding': 1,
  999. 'controls': 0
  1000. },
  1001. events: {
  1002. 'onReady': this.onPlayerReady
  1003. }
  1004. });
  1005.  
  1006. this.playerContainer = document.createElement('div');
  1007. this.playerContainer.setAttribute('class', 'swmp-player-container');
  1008.  
  1009. this.container.appendChild(this.playerContainer);
  1010.  
  1011. this.prepareSettings();
  1012. this.prepareControls();
  1013. this.prepareSharedEvents();
  1014. this.prepareYoutubeEvents();
  1015.  
  1016. if (!this.container.classList.contains('swmp-youtube')) { //cancel if already changed playlist
  1017. this.videogenerate.remove();
  1018. this.ytplayer.remove();
  1019. return false;
  1020. }
  1021.  
  1022. }
  1023.  
  1024.  
  1025. prepareYoutubeEvents() {
  1026.  
  1027. this.togglePlay = function() {
  1028. if (this.loaded != true) {
  1029. console.log('Userscript: YT must load before togglePlay.');
  1030. return;
  1031. }
  1032. if (this.ytplayer.getPlayerState() == 1) { // Playing
  1033. this.ytplayer.pauseVideo();
  1034. } else { // -1 unstarted, 0 ended, 1 playing, 2 Pause, 3 buffering, 5 video cued
  1035. this.ytplayer.playVideo();
  1036. this.updateTime();
  1037. }
  1038. }
  1039.  
  1040. this.seekBack = function() {
  1041. var time = this.ytplayer.getCurrentTime();
  1042. var max = this.ytplayer.getDuration();
  1043. if (time < 5) {
  1044. time = 0;
  1045. } else {
  1046. time = time - 5;
  1047. }
  1048. this.ytplayer.seekTo(time);
  1049.  
  1050. this.seeker.value = Math.floor(time / max * this.seeker.max);
  1051. this.seeker.setAttribute('value', this.seeker.value);
  1052. this.progress.value = this.seeker.value;
  1053. this.progress.setAttribute('value', this.seeker.value);
  1054. }
  1055.  
  1056. this.seekForward = function() {
  1057. var time = this.ytplayer.getCurrentTime();
  1058. var max = this.ytplayer.getDuration();
  1059. if (time+5 >= max ) {
  1060. time = max;
  1061. } else {
  1062. time = time + 5;
  1063. }
  1064. this.ytplayer.seekTo(time);
  1065.  
  1066. this.seeker.value = Math.floor(time / max * this.seeker.max);
  1067. this.seeker.setAttribute('value', this.seeker.value);
  1068. this.progress.value = this.seeker.value;
  1069. this.progress.setAttribute('value', this.seeker.value);
  1070.  
  1071. }
  1072.  
  1073. var timeInterval;
  1074. this.updatePlay = () => {
  1075.  
  1076. this._playerState = this.ytplayer.getPlayerState();
  1077.  
  1078. if (this._playerState == 1) { // Playing
  1079. this.playbutton.classList.add('swmp-playing');
  1080. this.updateTime();
  1081.  
  1082.  
  1083. var self = this;
  1084. var timeInterval = window.setInterval((event) => {
  1085. self._playerState = self.ytplayer.getPlayerState();
  1086. if (self._playerState == 1) {
  1087. self.updateTime();
  1088. }
  1089. if (self._playerState == 0) { //Ended
  1090. self.updatePlay();
  1091. }
  1092. //console.log('time'); //still bugged on close unlike the other interval, does close on playlist change
  1093. if (self.type != 'youtube' || self._playerState != 1 || self.container == null || self.container == undefined || self.container == false) {
  1094. clearInterval(timeInterval);
  1095. timeInterval = null;
  1096. }
  1097. }, 40);
  1098.  
  1099. } else if (this._playerState == 0 || this._playerState == -1 || this._playerState == 2 ) { // -1 unstarted, 0 ended, 1 playing, 2 Pause, 3 buffering, 5 video cued
  1100. this.playbutton.classList.remove('swmp-playing');
  1101. }
  1102.  
  1103. if (this._playerState == 0 || this._playerState == -1 ) {
  1104. this.progress.value = 0;
  1105. this.progress.setAttribute('value', 0);
  1106. this.seeker.value = 0;
  1107. this.seeker.setAttribute('value', 0);
  1108. }
  1109.  
  1110. if (this._playerState == 0) {
  1111.  
  1112. if (this.playlist != undefined && swmpConfig.loop == 'false' && swmpConfig.autoplay == 'true') {
  1113. this.nextMedia();
  1114. return;
  1115. }
  1116.  
  1117. if (swmpConfig.loop == 'true') {
  1118. console.log('SWMP: uwah video loop');
  1119. this.ytplayer.seekTo(0);
  1120. this.ytplayer.playVideo();
  1121. }
  1122. }
  1123.  
  1124. }
  1125.  
  1126. this.seeker.oninput = (event) => {
  1127. if (this.loaded != true) {
  1128. console.log('Userscript: Video must load before seeking');
  1129. return;
  1130. }
  1131. //on mousedown temporary add a mute to avoid annoying seeking sounds?
  1132. this.ytplayer.seekTo( (this.ytplayer.getDuration() * this.seeker.value / this.seeker.max) );
  1133. this.progress.value = this.seeker.value;
  1134. this.progress.setAttribute('value', this.seeker.value);
  1135. this.updatePlay();
  1136. }
  1137.  
  1138. this.updateTime = () => {
  1139. this.seeker.value = Math.floor(this.ytplayer.getCurrentTime() / this.ytplayer.getDuration() * this.seeker.max);
  1140. this.seeker.setAttribute('value', this.seeker.value);
  1141. this.progress.value = this.seeker.value;
  1142. this.progress.setAttribute('value', this.seeker.value);
  1143. }
  1144.  
  1145.  
  1146. this.toggleMute = () => {
  1147.  
  1148. try {
  1149. if (this.ytplayer.isMuted() == true ) {
  1150. this.volumeButton.classList.remove('swmp-mute');
  1151. this._volume = this.ytplayer.getVolume() ;
  1152. this.ytplayer.unMute();
  1153. this.ytplayer.setVolume(this._volume);
  1154. this.volumeRange.setAttribute('value', this._volume );
  1155. this.volumeRange.value = this._volume;
  1156. this.volumeProgress.setAttribute('value', this._volume );
  1157. this.volumeProgress.value = this._volume;
  1158. swmpConfig.muted = 'false';
  1159. localStorage.swmpMuted = 'false';
  1160. } else {
  1161. this.volumeButton.classList.add('swmp-mute');
  1162. this.ytplayer.mute();
  1163. this.volumeRange.setAttribute('value', 0 );
  1164. this.volumeRange.value = 0;
  1165. this.volumeProgress.setAttribute('value', 0 );
  1166. this.volumeProgress.value = 0;
  1167. swmpConfig.muted = 'true';
  1168. localStorage.swmpMuted = 'true';
  1169. }
  1170. } catch(e) {
  1171. console.log('Userscript: '+e);
  1172. }
  1173. }
  1174.  
  1175. this.updateVolume = (firstrun = false) => {
  1176.  
  1177. if (this.loaded != true) {
  1178. return;
  1179. }
  1180.  
  1181. this.volumeButton.classList.remove('swmp-mute');
  1182.  
  1183. if (firstrun == true) {
  1184. this.volumeRange.setAttribute('value', this.defaultVolume);
  1185. this.volumeProgress.setAttribute('value', this.defaultVolume);
  1186. this._volume = this.defaultVolume;
  1187. this.volumeButton.classList.add('swmp-min');
  1188. }
  1189.  
  1190. this._volume = this.ytplayer.getVolume();
  1191.  
  1192. this.volumeRange.setAttribute('value', this.volumeRange.value );
  1193. this.volumeProgress.setAttribute('value', this.volumeRange.value );
  1194. this.ytplayer.setVolume(this.volumeRange.value);
  1195.  
  1196. if (this._volume > 50) {
  1197. this.volumeButton.classList.add('swmp-max');
  1198. this.volumeButton.classList.remove('swmp-med');
  1199. this.volumeButton.classList.remove('swmp-min');
  1200. } else if (this._volume > 10) {
  1201. this.volumeButton.classList.add('swmp-med');
  1202. this.volumeButton.classList.remove('swmp-max');
  1203. this.volumeButton.classList.remove('swmp-min');
  1204. } else if (this._volume > 5) {
  1205. this.volumeButton.classList.add('swmp-min');
  1206. this.volumeButton.classList.remove('swmp-max');
  1207. this.volumeButton.classList.remove('swmp-med');
  1208. }
  1209.  
  1210. localStorage.swmpVolume = this.volumeRange.value;
  1211. swmpConfig.volume = this.volumeRange.value;
  1212. }
  1213.  
  1214. this.volumeRange.oninput = (event) => {
  1215. this.updateVolume();
  1216. }
  1217.  
  1218.  
  1219. }
  1220.  
  1221. prepareSharedEventsInit() {
  1222.  
  1223. this.container.addEventListener('mousemove', event => {
  1224. this.container.classList.add('swmp-movingmouse');
  1225. clearTimeout(this.mousemovetimeout);
  1226. this.mousemovetimeout = window.setTimeout((event) => {
  1227. this.container.classList.remove('swmp-movingmouse');
  1228. }, 1500);
  1229. });
  1230.  
  1231. this.volumeScroll = (event) => {
  1232. if (swmpConfig.volumeScroll != 'true') {
  1233. return false;
  1234. }
  1235. event.preventDefault();
  1236. if (this.type == 'video' || this.type == 'audio') {
  1237. if (event.wheelDelta > 0) {
  1238. var vol = this.player.volume + 0.04;
  1239. if (vol > 1.0) {
  1240. vol = 1.0;
  1241. }
  1242.  
  1243. } else {
  1244. var vol = this.player.volume - 0.04;
  1245. if (vol < 0.0) {
  1246. vol = 0.0;
  1247. }
  1248. }
  1249. var newvolume = Math.floor(vol * 100);
  1250.  
  1251. } else if (this.type == 'youtube') {
  1252. if (event.wheelDelta > 0) {
  1253. var vol = this.ytplayer.getVolume() + 4;
  1254. if (vol > 100) {
  1255. vol = 100;
  1256. }
  1257.  
  1258. } else {
  1259. var vol = this.ytplayer.getVolume() - 4;
  1260. if (vol < 0) {
  1261. vol = 0;
  1262. }
  1263. }
  1264. var newvolume = vol;
  1265. }
  1266.  
  1267. this.volumeProgress.setAttribute('value', newvolume);
  1268. this.volumeProgress.value = newvolume;
  1269. this.volumeRange.setAttribute('value', newvolume);
  1270. this.volumeRange.value = newvolume;
  1271. this.updateVolume(); // Change icon blah blah
  1272. }
  1273.  
  1274. this.container.addEventListener('wheel', event => {
  1275. this.volumeScroll(event);
  1276. });
  1277.  
  1278. this.container.addEventListener('keydown', event => {
  1279. event.stopPropagation();
  1280. event.preventDefault();
  1281. if (event.repeat) { return; } // Don't spam
  1282. switch(event.key) {
  1283. case ' ': //Space
  1284. this.togglePlay();
  1285. break;
  1286. case 'f':
  1287. this.toggleFullscreen();
  1288. break;
  1289. case 'm':
  1290. this.toggleMute();
  1291. break;
  1292. case 'x':
  1293. this.container.remove();
  1294. break;
  1295. /*case 'q': // still need do youtube fix for fast skipping, otherwise would work well
  1296. if (this.playlist != undefined) { this.previousMedia(); } else { return; }
  1297. break;
  1298. case 'e':
  1299. if (this.playlist != undefined) { this.nextMedia(); } else { return; }
  1300. break;*/
  1301. case 'ArrowLeft': //left
  1302. this.seekBack();
  1303. break;
  1304. case 'ArrowRight': //right
  1305. this.seekForward();
  1306. break;
  1307. case 'Escape':
  1308. // assuming i wont need to call exitFullscreen, all browsers default to escape escaping, right?
  1309. this.container.classList.remove('swmp-fullscreen');
  1310. this.container.classList.remove('swmp-maximized');
  1311. break;
  1312. default:
  1313. return;
  1314. }
  1315. }, true);
  1316.  
  1317. this.isFullscreen = () => {
  1318. var full = document.fullscreenElement ||
  1319. document.mozFullScreenElement ||
  1320. document.webkitFullscreenElement ||
  1321. document.msFullscreenElement;
  1322. if (full != undefined) {
  1323. return true;
  1324. } else {
  1325. return false;
  1326. }
  1327. }
  1328.  
  1329. this.container.addEventListener('fullscreenchange', event => {
  1330. if (this.isFullscreen() == true) {
  1331. this.container.classList.add('swmp-fullscreen');
  1332. } else {
  1333. this.container.classList.remove('swmp-fullscreen');
  1334. this.container.classList.remove('swmp-maximized');
  1335. }
  1336. });
  1337.  
  1338. this.container.onfullscreenerror = (event) => {
  1339. console.log('SWMP: Fullscreen error.');
  1340. this.toggleMaximize(); // maximize to cope.
  1341. this.container.classList.remove('swmp-fullscreen');
  1342. return;
  1343. };
  1344.  
  1345. this.requestFullscreen = (element) => {
  1346. try {
  1347. if (element.requestFullscreen) {
  1348. element.requestFullscreen();
  1349. } else if (element.mozRequestFullScreen) {
  1350. element.mozRequestFullScreen();
  1351. } else if (element.webkitRequestFullscreen) {
  1352. this._scroll = {'x' : window.scrollX, 'y' : window.scrollY}
  1353. element.webkitRequestFullscreen();
  1354. } else if (element.msRequestFullscreen) {
  1355. element.msRequestFullscreen();
  1356. } else {
  1357. throw new Error('SWMP: Could not request fullscreen.');
  1358. }
  1359. } catch(e) { // This doesn't seem to work on firefox, not tested others. Requests can't be caught.
  1360. // Also tried try on each individual scope, no luck.
  1361. // onfullscreenerror is fine, but still means uncaught error in console log.
  1362. console.log('SWMP: Fullscreen request denied by browser.');
  1363. console.log(e);
  1364. this.toggleMaximize();
  1365. return;
  1366. }
  1367. }
  1368.  
  1369. this.exitFullscreen = () => {
  1370. if (document.exitFullscreen) {
  1371. document.exitFullscreen();
  1372. } else if (document.mozCancelFullScreen) {
  1373. document.mozCancelFullScreen();
  1374. } else if (document.webkitExitFullscreen) {
  1375. window.scrollTo({
  1376. top: this._scroll['y'],
  1377. left: this._scroll['x'],
  1378. behavior: 'auto'
  1379. });
  1380. document.webkitExitFullscreen(); //Safari sucks
  1381. } else if (document.msExitFullscreen) {
  1382. document.msExitFullscreen();
  1383. }
  1384. this.container.classList.remove('swmp-fullscreen');
  1385. this.container.classList.remove('swmp-maximized');
  1386. }
  1387.  
  1388. this.toggleFullscreen = () => {
  1389. if (this.isFullscreen() == true) {
  1390. this.exitFullscreen();
  1391. } else {
  1392. this.requestFullscreen(this.container);
  1393. }
  1394. }
  1395.  
  1396. }
  1397.  
  1398. toggleMaximize = (event) => {
  1399. if (event) {
  1400. event.preventDefault();
  1401. }
  1402.  
  1403. // Remove fullscreen if toggle off maximize in fullscreen
  1404. if (this.isFullscreen() == true) {
  1405. this.exitFullscreen();
  1406. return;
  1407. }
  1408.  
  1409. if (this.type == 'audio' && this.playlist == null) { //allow on playlist, disallow on audio only
  1410. this.container.classList.remove('swmp-maximized');
  1411. return;
  1412. }
  1413. this.container.classList.toggle('swmp-maximized');
  1414. }
  1415.  
  1416. prepareSharedEvents() { // Redo on player change
  1417.  
  1418. this.playerContainer.addEventListener('click', (event) => {
  1419. this.togglePlay();
  1420. });
  1421.  
  1422. this.playerContainer.addEventListener('dblclick', (event) => {
  1423.  
  1424. if (swmpConfig.doubleclickMaximize == 'true') {
  1425. this.toggleMaximize();
  1426. return;
  1427. }
  1428.  
  1429. this.toggleFullscreen();
  1430. });
  1431.  
  1432. }
  1433.  
  1434. preparePlayerEvents() {
  1435.  
  1436.  
  1437. this.player.onended = (event) => {
  1438. this.player.classList.remove('swmp-playing');
  1439. this.playbutton.classList.remove('swmp-playing');
  1440. this.seeker.value = 0;
  1441. this.progress.value = 0;
  1442. clearInterval(this.player.interval);
  1443. this.currentTimer.textContent = '00:00';
  1444. this.loaded = false;
  1445. if (this.playlist != undefined && swmpConfig.loop == 'false' && swmpConfig.autoplay == 'true') { //doesnt activate on loop
  1446. this.nextMedia();
  1447. return;
  1448. }
  1449. }
  1450.  
  1451. this.player.addEventListener('loadedmetadata', (event) => {
  1452. this.totalTimer.textContent = this.formatSeconds(this.player.duration);
  1453. });
  1454.  
  1455. this.player.addEventListener('loadeddata', (event) => {
  1456. // Video is loaded and can be played
  1457. this.loaded = true;
  1458. });
  1459.  
  1460.  
  1461. this.player.onplay = (event) => {
  1462. this.loaded = true;
  1463. this.player.classList.add('swmp-playing');
  1464. this.playbutton.classList.add('swmp-playing');
  1465. this.player.interval = window.setInterval((event) => {
  1466. //console.log('time');
  1467. this.player.timeupdate();
  1468. }, 40);
  1469. }
  1470.  
  1471. this.player.onpause = (event) => {
  1472. this.player.classList.remove('swmp-playing');
  1473. this.playbutton.classList.remove('swmp-playing');
  1474. clearInterval(this.player.interval);
  1475. }
  1476.  
  1477. this.player.onerror = (event) => {
  1478. clearInterval(this.player.interval);
  1479. var _error = this.player.error.message;
  1480. console.log(_error);
  1481. this.playerContainer.innerHTMl = _error;
  1482. }
  1483.  
  1484. this.player.timeupdate = (event) => {
  1485. this.seeker.value = Math.floor(this.player.currentTime / this.player.duration * this.seeker.max);
  1486. this.seeker.setAttribute("value", this.seeker.value);
  1487. this.progress.value = this.seeker.value;
  1488. this.progress.setAttribute("value", this.seeker.value);
  1489. this.updateTimer();
  1490. }
  1491.  
  1492. this.seeker.oninput = (event) => {
  1493. if (this.loaded != true) {
  1494. console.log('Userscript: Video status must be loaded first');
  1495. return;
  1496. }
  1497. try {
  1498. //on mousedown temporary add a mute to avoid annoying seeking sounds?
  1499. this.player.currentTime = (this.player.duration * this.seeker.value / this.seeker.max);
  1500. this.progress.value = this.seeker.value;
  1501. this.progress.setAttribute("value", this.seeker.value);
  1502. this.updateTimer();
  1503. } catch (e) {
  1504. console.log('Userscript: ' + e);
  1505. }
  1506. }
  1507.  
  1508. this.updateTimer = () => {
  1509. this.currentTimer.textContent = this.formatSeconds(this.player.currentTime);
  1510. //this.totalTimer.textContent = this.formatSeconds(this.player.duration);
  1511. }
  1512.  
  1513. this.seekBack = function() {
  1514. var time = this.player.currentTime;
  1515. var max = this.player.duration;
  1516. if (time < 5) {
  1517. time = 0;
  1518. } else {
  1519. time = time - 5;
  1520. }
  1521. this.player.currentTime = time;
  1522. this.updateTimer();
  1523.  
  1524. this.seeker.value = Math.floor(time / max * this.seeker.max);
  1525. this.seeker.setAttribute('value', this.seeker.value);
  1526. this.progress.value = this.seeker.value;
  1527. this.progress.setAttribute('value', this.seeker.value);
  1528. }
  1529.  
  1530. this.seekForward = function() {
  1531. var time = this.player.currentTime;
  1532. var max = this.player.duration;
  1533. if (time+5 >= max ) {
  1534. time = max;
  1535. } else {
  1536. time = time + 5;
  1537. }
  1538. this.player.currentTime = time;
  1539. this.updateTimer();
  1540.  
  1541. this.seeker.value = Math.floor(time / max * this.seeker.max);
  1542. this.seeker.setAttribute('value', this.seeker.value);
  1543. this.progress.value = this.seeker.value;
  1544. this.progress.setAttribute('value', this.seeker.value);
  1545.  
  1546. }
  1547.  
  1548. this.togglePlay = () => {
  1549. try {
  1550. if (this.player.paused ) {
  1551. this.player.play();
  1552. } else {
  1553. this.player.pause();
  1554. }
  1555. } catch (e) {
  1556. console.log('Userscript: ' + e)
  1557. }
  1558. }
  1559.  
  1560. this.toggleMute = () => {
  1561. if (this.player.muted) {
  1562. this.volumeButton.classList.remove('swmp-mute');
  1563. this.player.muted = false;
  1564. this._volume = this.player.volume;
  1565. this.volumeRange.setAttribute('value', this._volume * 100 );
  1566. this.volumeRange.value = this._volume * 100;
  1567. this.volumeProgress.setAttribute('value', this._volume * 100 );
  1568. this.volumeProgress.value = this._volume * 100;
  1569. swmpConfig.muted = 'false';
  1570. localStorage.swmpMuted = 'false';
  1571. } else {
  1572. this.volumeButton.classList.add('swmp-mute');
  1573. this.player.muted = true;
  1574. this.volumeRange.setAttribute('value', 0 );
  1575. this.volumeRange.value = 0;
  1576. this.volumeProgress.setAttribute('value', 0 );
  1577. this.volumeProgress.value = 0;
  1578. swmpConfig.muted = 'true';
  1579. localStorage.swmpMuted = 'true';
  1580. }
  1581. //console.log(this._volume);
  1582. }
  1583.  
  1584. this.updateVolume = (firstrun = false) => {
  1585.  
  1586. this.volumeButton.classList.remove('swmp-mute');
  1587.  
  1588. if (firstrun == true) {
  1589. this.volumeRange.setAttribute('value', this.defaultVolume);
  1590. this.volumeProgress.setAttribute('value', this.defaultVolume);
  1591. this.volumeButton.classList.add('swmp-min');
  1592. this._volume = this.defaultVolume;
  1593.  
  1594. }
  1595.  
  1596. if (this.player.muted) {
  1597. this.player.muted = false;
  1598. }
  1599.  
  1600. this.volumeRange.setAttribute('value', this.volumeRange.value );
  1601. this.volumeProgress.setAttribute('value', this.volumeRange.value );
  1602. this.player.volume = parseFloat(this.volumeRange.value / 100);
  1603. this._volume = this.player.volume;
  1604.  
  1605. if (this._volume > 0.50) {
  1606. this.volumeButton.classList.add('swmp-max');
  1607. this.volumeButton.classList.remove('swmp-med');
  1608. this.volumeButton.classList.remove('swmp-min');
  1609. } else if (this._volume > 0.10) {
  1610. this.volumeButton.classList.add('swmp-med');
  1611. this.volumeButton.classList.remove('swmp-max');
  1612. this.volumeButton.classList.remove('swmp-min');
  1613. } else if (this._volume > 0.05) {
  1614. this.volumeButton.classList.add('swmp-min');
  1615. this.volumeButton.classList.remove('swmp-max');
  1616. this.volumeButton.classList.remove('swmp-med');
  1617. }
  1618.  
  1619. //console.log(this._volume);
  1620.  
  1621. localStorage.swmpVolume = this.volumeRange.value;
  1622. swmpConfig.volume = this.volumeRange.value;
  1623. }
  1624.  
  1625. this.volumeRange.oninput = (event) => {
  1626. this.updateVolume();
  1627. }
  1628.  
  1629. this.player.addEventListener('volumechange', this.updateVolume() );
  1630. this.updateVolume(true); // First time Run
  1631. }
  1632.  
  1633. formatSeconds = (seconds) => {
  1634. this._sec_num = parseInt(seconds, 10);
  1635. this._hours = Math.floor(this._sec_num / 3600);
  1636. this._minutes = Math.floor(this._sec_num / 60) % 60;
  1637. this._seconds = this._sec_num % 60;
  1638.  
  1639. return [this._hours,this._minutes,this._seconds]
  1640. .map(v => v < 10 ? "0" + v : v)
  1641. .filter((v,i) => v !== "00" || i > 0)
  1642. .join(":");
  1643. }
  1644.  
  1645. removeClassByPrefix(el, prefix) {
  1646. let pattern = '(' + prefix + '(\\s|(-)?(\\w*)(\\s)?)).*?';
  1647. var regEx = new RegExp(pattern, 'g');
  1648. el.className = el.className.replace(regEx, '');
  1649. }
  1650.  
  1651. uuid() {
  1652. // Source: https://www.w3resource.com/javascript-exercises/javascript-math-exercise-23.php
  1653. var dt = new Date().getTime();
  1654. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  1655. var r = (dt + Math.random()*16)%16 | 0;
  1656. dt = Math.floor(dt/16);
  1657. return (c=='x' ? r :(r&0x3|0x8)).toString(16);
  1658. });
  1659. return uuid;
  1660. }
  1661.  
  1662. getFileName(url) {
  1663. return url.split('/').pop().split('#')[0].split('?')[0];
  1664. }
  1665.  
  1666. checkURL(url) {
  1667.  
  1668. // if match youtube regex then do this and return
  1669. var result = ytregex.exec(url);
  1670. //console.log(ytregex);
  1671. //console.log(url);
  1672. //console.log(result);
  1673. if (url.match(ytregex) ) {
  1674. console.log('SWMP: YouTube');
  1675. this.type = 'youtube';
  1676. this.videoid = result[1];
  1677. console.log('SWMP: Youtube ID: '+ this.videoid);
  1678. return true;
  1679. }
  1680.  
  1681. console.log("SWMP: Video or Audio");
  1682. // Check filename in URL for Type / MIME.
  1683. this.fileExt = url.split(/[#?&]/)[0].split('.').pop().trim();
  1684. this.fileExt = url.split(/[#?&]/).slice(-2)[0].split('.').pop().trim();
  1685.  
  1686.  
  1687. console.log(this.fileExt);
  1688. this.fileExt = this.fileExt.toLowerCase();
  1689.  
  1690. switch (this.fileExt) {
  1691. //VIDEO
  1692. case 'avi':
  1693. this.type = 'video';
  1694. this.mime = 'video/x-msvideo';
  1695. break;
  1696. case 'mpeg':
  1697. this.type = 'video';
  1698. this.mime = 'video/mpeg';
  1699. break;
  1700. case 'mpg':
  1701. this.type = 'video';
  1702. this.mime = 'video/mpeg';
  1703. break;
  1704. case 'ogv':
  1705. this.type = 'video';
  1706. this.mime = 'video/ogg';
  1707. break;
  1708. case 'mp4':
  1709. this.type = 'video';
  1710. this.mime = 'video/mp4';
  1711. break;
  1712. case 'webm':
  1713. this.type = 'video';
  1714. this.mime = 'video/webm';
  1715. break;
  1716. case 'flv':
  1717. this.type = 'video';
  1718. this.mime = 'video/x-flv';
  1719. break;
  1720. //AUDIO
  1721. case 'wav':
  1722. this.type = 'audio';
  1723. this.mime = 'audio/x-wav';
  1724. break;
  1725. case 'mp3':
  1726. this.type = 'audio';
  1727. this.mime = 'audio/mpeg';
  1728. break;
  1729. case 'm4a':
  1730. this.type = 'audio';
  1731. this.mime = 'audio/mp4';
  1732. break;
  1733. case 'mp2':
  1734. this.type = 'audio';
  1735. this.mime = 'audio/mpeg';
  1736. break;
  1737. case 'ogg':
  1738. this.type = 'audio';
  1739. this.mime = 'audio/ogg';
  1740. break;
  1741. case 'flac':
  1742. this.type = 'audio';
  1743. this.mime = 'audio/flac';
  1744. break;
  1745. default:
  1746. console.log('No matching ext/mime');
  1747. console.log(this.fileExt);
  1748. return false;
  1749. }
  1750. return true; //If valid
  1751. }
  1752.  
  1753. makeDraggable (element){
  1754. var elm = this.windowTitlebar;
  1755.  
  1756. element.style.position = 'fixed';
  1757. element.style.top = swmpConfig.positionTop + 'px';
  1758.  
  1759. if (swmpConfig.positionSide == 'right') {
  1760. element.style.right = swmpConfig.positionOffset + 'px';
  1761. } else {
  1762. element.style.left = swmpConfig.positionOffset + 'px';
  1763. }
  1764.  
  1765. var isMouseDown = false,
  1766. mouseX,
  1767. mouseY,
  1768. elmTop,
  1769. elmLeft,
  1770. diffX,
  1771. newElmTop,
  1772. newElmLeft,
  1773. diffY,
  1774. rightBarrier,
  1775. bottomBarrier;
  1776.  
  1777. function mouseDown(e) {
  1778.  
  1779. if (e.which == '3') {
  1780. return false; // Right click
  1781. }
  1782.  
  1783. element.focus();
  1784.  
  1785. isMouseDown = true;
  1786.  
  1787. mouseX = e.clientX;
  1788. mouseY = e.clientY;
  1789.  
  1790. elmTop = element.offsetTop;
  1791. elmLeft = element.offsetLeft;
  1792. diffX = mouseX - elmLeft;
  1793. diffY = mouseY - elmTop;
  1794.  
  1795. }
  1796.  
  1797. function mouseUp() {
  1798. isMouseDown = false;
  1799.  
  1800.  
  1801. if (swmpConfig.positionSide == 'right') {
  1802. var currentleft = parseInt(element.style.left, 10);
  1803. element.style.left = "unset";
  1804. element.style.right = document.documentElement.clientWidth - currentleft - element.offsetWidth + "px";
  1805. }
  1806.  
  1807. }
  1808.  
  1809. function mouseMove(e) {
  1810.  
  1811.  
  1812. if (!isMouseDown) return;
  1813.  
  1814. if (e.which != '1') {
  1815. isMouseDown = false;
  1816. }
  1817.  
  1818. if (element.classList.contains('swmp-maximized') ) return;
  1819. //element.classList.remove('swmp-maximized');
  1820.  
  1821. var newMouseX = e.clientX;
  1822. var newMouseY = e.clientY;
  1823.  
  1824. newElmTop = newMouseY - diffY;
  1825. newElmLeft = newMouseX - diffX;
  1826.  
  1827. rightBarrier = document.documentElement.clientWidth - element.offsetWidth;
  1828. bottomBarrier = window.innerHeight - element.offsetHeight;
  1829.  
  1830. if( ( newElmLeft < 0 ) || ( newElmTop < 0) || ( newElmLeft > rightBarrier ) || (newElmTop > bottomBarrier) ) {
  1831. if ( newElmLeft < 0 ) {
  1832. newElmLeft = 0;
  1833.  
  1834. }
  1835.  
  1836. if ( newElmTop < 0) {
  1837. newElmTop = 0;
  1838.  
  1839. }
  1840. if ( newElmLeft > rightBarrier ) {
  1841. newElmLeft = rightBarrier;
  1842.  
  1843. }
  1844. if (newElmTop > bottomBarrier) {
  1845. newElmTop = bottomBarrier;
  1846. }
  1847. }
  1848.  
  1849. element.style.top = newElmTop + "px";
  1850. element.style.bottom = "unset";
  1851.  
  1852. if (element.offsetTop < 0) {
  1853. element.style.top = "0px";
  1854. }
  1855.  
  1856. if (swmpConfig.positionSide == 'right') {
  1857. element.style.left = newElmLeft + "px";
  1858. element.style.right = "unset";
  1859. } else {
  1860. element.style.left = newElmLeft + "px";
  1861. }
  1862.  
  1863. }
  1864.  
  1865. document.addEventListener('mousemove', mouseMove);
  1866. document.addEventListener('mouseup', mouseUp);
  1867. elm.addEventListener('mousedown', mouseDown);
  1868. }
  1869.  
  1870. }
  1871.  
  1872. // Above this is SWMP class.
  1873.  
  1874.  
  1875. // Below this is the event listener that checks if what you are clicking on is a video or audio that should be put into SWMP.
  1876. // If you want to make site specific changes, either expand on it or just make a completely separate event listener below it.
  1877.  
  1878. document.addEventListener('click', (event) => {
  1879. //console.log("Userscript - "+event.target.tagName);
  1880.  
  1881. // If there are no links in sight, do nothing.
  1882. if (event.target.tagName != 'A' && event.target.parentNode.tagName != 'A') {
  1883. return;
  1884. }
  1885. // Lets figure out which one is the link. Wont normally chain As inside As so... Sometimes image may be inside span inside A so lets do 3x.
  1886. var clicked = false;
  1887. if (event.target.tagName == 'A') {
  1888. clicked = event.target;
  1889. } else if (event.target.parentNode.tagName == 'A') {
  1890. clicked = event.target.parentNode;
  1891. } else if (event.target.parentNode.parentNode.tagName == 'A') {
  1892. clicked = event.target.parentNode.parentNode;
  1893. }
  1894.  
  1895. // Download attribute override
  1896. if (swmpConfig.downloadAttribute == 'false' && clicked.hasAttribute('download')) {
  1897. return;
  1898. }
  1899.  
  1900. function getVideo() {
  1901. if (backendScript == '' || backendScript == null || backendScript == undefined || backendScript == 'fourchan') {
  1902. return clicked.getAttribute('href');
  1903. } else if (backendScript == 'vichan') {
  1904. //console.log("Userscript - "+clicked.parentNode.querySelectorAll('p.fileinfo a') );
  1905.  
  1906. var allthelinks = clicked.parentNode.querySelectorAll('p.fileinfo a'); // Prevent capturing the (Hide) link on some installs
  1907. var matchinglink = '';
  1908. for (var i = 0; i < allthelinks.length ; i++) {
  1909. if (allthelinks[i].getAttribute('href').match(fileregex) ) {
  1910. matchinglink = allthelinks[i];
  1911. }
  1912. }
  1913.  
  1914. if (matchinglink == '') { // Kissu New UI (no thanks, but here it is anyways) <- This should also return normal href if not video?
  1915. matchinglink = clicked;
  1916. }
  1917.  
  1918. //console.log("Userscript - "+matchinglink);
  1919. return matchinglink.getAttribute('href');
  1920.  
  1921. } else {
  1922. return clicked.getAttribute('href');
  1923. }
  1924.  
  1925. }
  1926.  
  1927. function getFileNameFromUrl(link) {
  1928. if (backendScript == '' || backendScript == null || backendScript == undefined || backendScript == 'fourchan') {
  1929. return link;
  1930. } else if (backendScript == 'vichan') {
  1931. return link;
  1932. } else {
  1933. return link;
  1934. }
  1935.  
  1936. }
  1937.  
  1938. function getTitle(link) {
  1939. if (backendScript == 'fourchan') {
  1940. if (link.parentNode.querySelector('div.fileText a') != null) {
  1941. if (link.parentNode.querySelector('div.fileText a').getAttribute('title') ) {
  1942. return link.parentNode.querySelector('div.fileText a').getAttribute('title');
  1943. } else {
  1944. return link.parentNode.querySelector('div.fileText a').innerText;
  1945. }
  1946. } else {
  1947. return link.getAttribute('href');
  1948. }
  1949. }
  1950.  
  1951. if (backendScript == 'vichan') { // https://regex101.com/r/HivDYB/3
  1952. let regex = new RegExp(`((?:[^\.|^\/|^\=|^\n|^\&])+(?:\.)(?:${swmpConfig.files}))(?:[^(\w)]|$)`, 'gmi'); // Tests for www.webmaster.com/test.webm domain too.
  1953. var newlink = [...link.matchAll(regex)]; // Remove query behind *final filename* &title for old filename on vichan will stay.
  1954. return newlink.slice(-1)[0][1]; // return last in array, grabs t= title for vichan player.php links.
  1955. }
  1956. }
  1957.  
  1958. var clickedHref;
  1959. var clickedFileName;
  1960. var clickedTitle;
  1961. var anythingmatch;
  1962.  
  1963. function isYoutube(link) {
  1964. var result = ytregex.exec(link);
  1965.  
  1966. if (link.match(ytregex) != null ) {
  1967. anythingmatch = true;
  1968. console.log('Userscript - YouTube: '+result[1]); // Video ID
  1969. clickedTitle = 'YouTube: ' + result[1];
  1970. return true;
  1971. } else {
  1972. console.log('Userscript - Not YouTube');
  1973. return false;
  1974. }
  1975. }
  1976.  
  1977. // If youtube
  1978.  
  1979. if (isYoutube(clicked.getAttribute('href') ) ) {
  1980. console.log('Userscript - YouTube link clicked');
  1981. clickedHref = clicked.getAttribute('href');
  1982. //clickedTitle = 'Loading YouTube';
  1983. }
  1984.  
  1985. function isVideo(link) {
  1986. var result = fileregex.exec(link);
  1987.  
  1988. if (link.match(fileregex) != null) {
  1989. anythingmatch = true;
  1990. console.log('Userscript - Video/Audio match:' + result);
  1991. return true;
  1992. } else {
  1993. console.log('Userscript - Not Video/Audio');
  1994. return false;
  1995. }
  1996. }
  1997.  
  1998. //get video links
  1999. clickedHref = getVideo();
  2000.  
  2001. if (isVideo(clickedHref) ) {
  2002. //clickedHref = getVideo();
  2003. clickedFileName = getFileNameFromUrl(clicked.getAttribute('href') );
  2004. clickedTitle = false;
  2005. if (backendScript == '' || backendScript == null || backendScript == undefined) {
  2006. clickedTitle = decodeURI(clickedFileName); //Change with other scripts later if it doesn't already contain the title.
  2007. } else if (backendScript == 'fourchan') {
  2008. clickedTitle = decodeURI(getTitle(clicked) );
  2009. } else if (backendScript == 'vichan') {
  2010. clickedTitle = getTitle(decodeURI(clicked.getAttribute('href') ) );
  2011. } else {
  2012. clickedTitle = decodeURI(clickedFileName); //Change with other scripts later if it doesn't already contain the title.
  2013. }
  2014.  
  2015. console.log("Userscript - URL: " + clicked.getAttribute('href') );
  2016. console.log("Userscript - Filename: " + clickedFileName );
  2017. console.log("Userscript - Title: " + clickedTitle);
  2018. }
  2019.  
  2020. if (anythingmatch != true) {
  2021. return false;
  2022. } else {
  2023. // If video audio or youtube matched, prevent other events.
  2024. event.preventDefault();
  2025. event.stopPropagation();
  2026. }
  2027.  
  2028. // todo:
  2029. // toggleable check if has download attribute?
  2030. // add Q/E shortcut for prev/next <- yt bugs on fast move
  2031.  
  2032. // Check if ctrl-shift-click and scan all links for yt/media, get titles,
  2033. // remove player.php + & tag from vichan to not get different url from link and thumb
  2034. // strip duplicates, feed to playlist WITH title
  2035. // Guess I'll need the ability to push urls+title to the player,
  2036. // lets send ["url", "title"] also adapt title get on playlist to grab from playlist
  2037.  
  2038.  
  2039. // Okay, time to load the player.
  2040. var playerid = false;
  2041.  
  2042. if (swmpConfig.allowMultiple != 'false') {
  2043. playerid = `play-swmp-${clicked.getAttribute('href')}`;
  2044. } else {
  2045. playerid = 'play-swmp';
  2046. }
  2047.  
  2048. if (typeof(document.getElementById(playerid)) != 'undefined' && document.getElementById(playerid) != null) {
  2049. if (swmpConfig.allowMultiple != 'false') {
  2050. return false; //already exists, lets do nothing.
  2051. } else {
  2052. document.getElementById(playerid).remove();
  2053. //already exists, lets get rid of it.
  2054. }
  2055. }
  2056.  
  2057. //console.log(playerid+clickedHref+clickedTitle);
  2058. let newembed = new swmp({
  2059. id: playerid,
  2060. url: clickedHref,
  2061. title: clickedTitle
  2062. });
  2063.  
  2064. //console.log(backendScript);
  2065. if (backendScript == 'jschan') { // Might be useful for inline.
  2066. clicked.parentNode.parentNode.appendChild(newembed.container);
  2067. } else {
  2068. clicked.parentNode.appendChild(newembed.container);
  2069. //clicked.parentNode.insertBefore(newembed.container, clicked.nextSibling);
  2070. }
  2071.  
  2072. // Give focus to player for keybinds.
  2073. newembed.container.focus();
  2074.  
  2075.  
  2076. }, true); // Fuck other scripts.