4chan \ IB Simple Web Media Player

Simple Web Media Player for 4chan and other imageboards.

当前为 2022-08-18 提交的版本,查看 最新版本

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