4chan \ IB Simple Web Media Player

Simple Web Media Player for 4chan and other imageboards.

目前为 2022-09-01 提交的版本,查看 最新版本

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