Simple HTML5 video player

Replaces any default HTML5 player with custom controls

  1. // ==UserScript==
  2. // @name Simple HTML5 video player
  3. // @description Replaces any default HTML5 player with custom controls
  4. // @grant GM_addStyle
  5. // @include *
  6. // @run-at document-load
  7. // @version 8
  8. // @namespace https://greasyfork.org/users/3167
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. var videowrapper_init = false;
  13.  
  14. function HHMMSS(num) {
  15. num = num || 0;
  16. var sec_num = Math.floor(num);
  17.  
  18. var hours = Math.floor(sec_num / 3600);
  19. var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
  20. var seconds = sec_num - (hours * 3600) - (minutes * 60);
  21.  
  22. if (hours < 10) {hours = "0"+hours;}
  23. if (minutes < 10) {minutes = "0"+minutes;}
  24. if (seconds < 10) {seconds = "0"+seconds;}
  25.  
  26. if (hours<1) {
  27. return minutes+':'+seconds;
  28. }
  29. return hours+':'+minutes+':'+seconds;
  30. }
  31.  
  32. function fs_status() {
  33. if (document.fullscreenElement) {
  34. return 1;
  35. }
  36. else if (document.webkitFullscreenElement) {
  37. return 1;
  38. }
  39. else if (document.mozFullScreenElement) {
  40. return 1;
  41. }
  42. else return -1;
  43. }
  44.  
  45. function init_videowrapper() {
  46.  
  47. var videos = document.getElementsByTagName('video');
  48. for (var i=0; i<videos.length; i++) {
  49.  
  50. (function(video) {
  51. if (video.controls==true && !video.iswrapped) {
  52. var spacing = 4;
  53. var computedStyle = window.getComputedStyle(video);
  54. video.controls_timeout=false;
  55. video.controls=false;
  56. video.iswrapped=true;
  57.  
  58. var videowrapper = document.createElement('videowrapper');
  59. videowrapper.className=video.className;
  60.  
  61. var showui = function() {
  62. videowrapper.classList.add("showui");
  63. }
  64. var hideui = function() {
  65. videowrapper.classList.remove("showui");
  66. }
  67. var peekui = function(duration) {
  68. duration = duration || 1000;
  69. showui();
  70. if (video.controls_timeout) {
  71. clearTimeout(video.controls_timeout);
  72. }
  73. video.controls_timeout = setTimeout(hideui, duration);
  74. }
  75. var showosd = function() {
  76. videowrapper.classList.add("showosd");
  77. }
  78. var hideosd = function() {
  79. videowrapper.classList.remove("showosd");
  80. }
  81. var peekosd = function(duration) {
  82. duration = duration || 1000;
  83. showosd();
  84. if (video.osd_timeout) {
  85. clearTimeout(video.osd_timeout);
  86. }
  87. video.osd_timeout = setTimeout(hideosd, duration);
  88. }
  89. var setosd = function(content) {
  90. videowrapper.setAttribute("data-osd", content)
  91. }
  92. setosd('Loading');
  93. if (computedStyle.position == "absolute") {
  94. videowrapper.className="absolute";
  95. } else {
  96. videowrapper.className="relative";
  97. }
  98. if (video.height>0) {
  99. videowrapper.style.height=video.height;
  100. }
  101. if (video.width>0) {
  102. videowrapper.style.width=video.width;
  103. }
  104. video.classList.add("iswrapped");
  105.  
  106. if (video.parentNode==document.body && document.body.childNodes.length==1) {
  107. //document.body.style.display="flex";
  108. //document.body.style.alignItems="center";
  109. //document.body.style.justifyContent="center";
  110. //document.body.style.margin="auto";
  111. //document.body.style.height="100vh";
  112. document.body.className="videobody";
  113. }
  114.  
  115. if (video.parentNode!=videowrapper) {
  116. video.parentNode.insertBefore(videowrapper, video);
  117. videowrapper.appendChild(video);
  118. }
  119.  
  120. var controls = document.createElement('controls');
  121. video.parentNode.insertBefore(controls, video.nextSibling);
  122.  
  123. var playbutton = document.createElement('button');
  124. controls.appendChild(playbutton);
  125. playbutton.innerHTML="&#x25b6;";
  126.  
  127. playbutton.style.left = spacing + "px";
  128.  
  129. var timestamp = document.createElement('span');
  130. controls.appendChild(timestamp);
  131. timestamp.innerHTML="0:00/0:00";
  132.  
  133. timestamp.style.left = (spacing + playbutton.clientWidth + spacing) + "px";
  134.  
  135. var fsbutton = document.createElement('button');
  136. controls.appendChild(fsbutton);
  137. fsbutton.innerHTML="&#x25a1;";
  138.  
  139. fsbutton.style.right = spacing + "px";
  140.  
  141. var volumebar = document.createElement('input');
  142. controls.appendChild(volumebar);
  143. volumebar.type="range";
  144. volumebar.min=0;
  145. volumebar.max=1;
  146.  
  147. volumebar.step=0.01;
  148. volumebar.value=0.5;
  149. volumebar.innerHTML="";
  150.  
  151. volumebar.style.width="50px";
  152. volumebar.style.right= (spacing + fsbutton.clientWidth + spacing) + "px";
  153.  
  154. var mutebutton = document.createElement('button');
  155. controls.appendChild(mutebutton);
  156. mutebutton.innerHTML="&#x1f50a;";
  157. mutebutton.style.right=(spacing + volumebar.clientWidth + spacing + fsbutton.clientWidth + spacing) + "px";
  158.  
  159. var seekbar = document.createElement('input');
  160. controls.appendChild(seekbar);
  161. seekbar.type="range";
  162. seekbar.value=0;
  163. seekbar.step=0.01;
  164. seekbar.innerHTML="";
  165.  
  166. seekbar.hidden = true;
  167.  
  168. var header = document.createElement('header');
  169. video.parentNode.insertBefore(header, video.nextSibling);
  170.  
  171. var label = document.createElement('label');
  172. header.appendChild(label);
  173. label.innerHTML="Loading...";
  174.  
  175. /*
  176. var savebutton = document.createElement('a');
  177. controls.appendChild(savebutton);
  178. savebutton.innerHTML="&#x1f847;";
  179. savebutton.style.lineHeight="32px";
  180. savebutton.style.position="absolute";
  181. savebutton.style.right="8px";
  182. savebutton.style.bottom="0";
  183. savebutton.style.border="none";
  184. savebutton.style.paddingTop="0";
  185. savebutton.style.paddingBottom="0";
  186. savebutton.style.paddingLeft="4px";
  187. savebutton.style.paddingRight="4px";
  188. savebutton.style.background="none";
  189. savebutton.style.fontFamily="Segoe UI Symbol";
  190. savebutton.style.fontSize="18px";
  191. savebutton.style.margin="0";
  192. savebutton.style.height="32px";
  193.  
  194. savebutton.href=video.currentSrc;
  195. savebutton.download="";
  196. */
  197. var playvideo = function() {
  198. video.play();
  199. if (video.livemode) {
  200. video.currentTime = video.duration - 3;
  201. }
  202. }
  203.  
  204. playbutton.addEventListener("click", function() {
  205. if (video.paused == true) {
  206. playvideo();
  207. } else {
  208. video.pause();
  209. }
  210. });
  211.  
  212. video.addEventListener("click", function() {
  213. if (video.paused == true) {
  214. playvideo();
  215. } else {
  216. if (!video.livemode) {
  217. video.pause();
  218. }
  219. }
  220. });
  221.  
  222. video.addEventListener("play", function() {
  223. setosd(playbutton.innerHTML);
  224. peekosd(1000);
  225. playbutton.innerHTML = "&#10074;&#10074;";
  226. //controls.className="playing";
  227. videowrapper.classList.remove("paused");
  228. videowrapper.classList.add("playing");
  229.  
  230. });
  231.  
  232. video.addEventListener("pause", function() {
  233. setosd(playbutton.innerHTML);
  234. peekosd(3000);
  235. playbutton.innerHTML = "&#x25b6;";
  236. //controls.className="paused";
  237. videowrapper.classList.remove("playing");
  238. videowrapper.classList.add("paused");
  239. });
  240. var updatetimestamp = function() {
  241. if (video.livemode) {
  242. var buffer = Math.round(video.duration - video.currentTime);
  243. timestamp.innerHTML = "Buffer: " + buffer + "s";
  244. } else {
  245. timestamp.innerHTML = HHMMSS(video.currentTime) + "/" + HHMMSS(video.duration);
  246. }
  247. }
  248.  
  249. video.addEventListener("timeupdate", function() {
  250. updatetimestamp();
  251. });
  252.  
  253. video.addEventListener("durationchange", function() {
  254. updatetimestamp();
  255. });
  256.  
  257. mutebutton.addEventListener("click", function() {
  258. if (video.muted == false) {
  259. video.muted = true;
  260. } else {
  261. video.muted = false;
  262. }
  263. });
  264.  
  265. var togglefs = function() {
  266. if (fs_status()>0) {
  267. if (document.exitFullscreen) {
  268. document.exitFullscreen();
  269. } else if (document.webkitExitFullscreen) {
  270. document.webkitExitFullscreen();
  271. } else if (document.mozCancelFullScreen) {
  272. document.mozCancelFullScreen();
  273. } else if (document.msExitFullscreen) {
  274. document.msExitFullscreen();
  275. }
  276. }
  277. else {
  278.  
  279. if (videowrapper.requestFullscreen) {
  280. videowrapper.requestFullscreen();
  281. } else if (videowrapper.mozRequestFullScreen) {
  282. videowrapper.mozRequestFullScreen(); // Firefox
  283. } else if (videowrapper.webkitRequestFullscreen) {
  284. videowrapper.webkitRequestFullscreen(); // Chrome and Safari
  285. }
  286. }
  287. };
  288. fsbutton.addEventListener("click", togglefs);
  289. videowrapper.addEventListener("dblclick", togglefs);
  290.  
  291. seekbar.addEventListener("input", function() {
  292. var time = video.duration * (seekbar.value / 100);
  293. video.currentTime = time;
  294.  
  295. });
  296.  
  297.  
  298. video.addEventListener("timeupdate", function() {
  299. var value = (100 / video.duration) * video.currentTime;
  300. var progress = (video.currentTime / video.duration);
  301. seekbar.value = value;
  302. //seekbar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + progress + ', var(--range-progress-color)), color-stop(' + progress + ', var(--range-background-color)))';
  303. });
  304.  
  305. var updatebuffer = function() {
  306. if (video.duration == Infinity || video.hls || video.livemode) {
  307. video.livemode = true;
  308. }
  309. if (video.livemode) {
  310. seekbar.hidden = true;
  311. label.innerHTML = "Live";
  312. } else {
  313. var start = video.buffered.length>0 ? ( video.buffered.start(0) / video.duration) : 0;
  314. var end = video.buffered.length>0 ? (video.buffered.end(0) / video.duration) : 0;
  315. seekbar.hidden = false;
  316. seekbar.style.left = (spacing + playbutton.clientWidth + spacing + timestamp.clientWidth + spacing) + "px";
  317. seekbar.style.right= (spacing + mutebutton.clientWidth + spacing + volumebar.clientWidth + spacing + fsbutton.clientWidth + spacing) + "px";
  318. seekbar.style.width='calc(100% - ' + seekbar.style.left + ' - ' + seekbar.style.right + ')';
  319. seekbar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + start + ', var(--range-background-color)), color-stop(' + start + ', var(--range-progress-color)), color-stop(' + end + ', var(--range-progress-color)), color-stop(' + end + ', var(--range-background-color)))';
  320.  
  321. if (video.currentSrc && video.currentSrc.length>0) {
  322. label.innerHTML = decodeURIComponent(video.currentSrc.split("/").pop());
  323. }
  324. }
  325.  
  326. if (video.videoHeight>0) {
  327. videowrapper.classList.remove("audiomode");
  328. videowrapper.classList.add("videomode");
  329. } else {
  330. videowrapper.classList.remove("videomode");
  331. videowrapper.classList.add("audiomode");
  332. }
  333.  
  334. };
  335.  
  336. video.addEventListener("timeupdate", updatebuffer);
  337. video.addEventListener("canplay", updatebuffer);
  338. video.addEventListener("progress", updatebuffer);
  339. video.addEventListener("canplaythrough", updatebuffer);
  340.  
  341. seekbar.addEventListener("mousedown", function() {
  342. seekbar.paused = video.paused;
  343. video.pause();
  344. });
  345.  
  346. // Play the video when the slider handle is dropped
  347. seekbar.addEventListener("mouseup", function() {
  348. if (!seekbar.paused) {
  349. video.play();
  350. }
  351. });
  352.  
  353. volumebar.addEventListener("input", function() {
  354. video.volume = volumebar.value;
  355. });
  356. video.addEventListener('wheel', function(e) {
  357. e.preventDefault();
  358. var volumedelta = 0.10;
  359. if (e.deltaY < 0) {
  360. video.volume = Math.min(video.volume+volumedelta, 1);
  361. }
  362. if (e.deltaY > 0) {
  363. video.volume = Math.max(video.volume-volumedelta, 0);
  364. }
  365. volumebar.value = video.volume;
  366.  
  367. var volumedata = Math.round(video.volume*100) + "%";
  368.  
  369. setosd(volumedata);
  370. peekosd(1000);
  371. });
  372.  
  373. var updatevolume = function() {
  374. if (video.muted || video.volume==0) {
  375. mutebutton.innerHTML = "&#x1f507;";
  376. } else {
  377. mutebutton.innerHTML = "&#x1f50a;";
  378. }
  379. volumebar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + video.volume + ', var(--range-progress-color)), color-stop(' + video.volume + ', var(--range-background-color)))';
  380. localStorage.setItem("videovolume", video.volume);
  381.  
  382. }
  383.  
  384. video.addEventListener("volumechange", updatevolume);
  385.  
  386. volumebar.value = localStorage.getItem("videovolume", video.volume);
  387. video.volume = volumebar.value;
  388.  
  389. videowrapper.addEventListener("mousemove", function() {
  390. peekui(1000);
  391. //seekbar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + progress + ', var(--range-progress-color)), color-stop(' + progress + ', var(--range-background-color)))';
  392. });
  393.  
  394. updatebuffer();
  395. updatevolume();
  396.  
  397. //seekbar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + seekbar.value / 100 + ', var(--range-progress-color)), color-stop(' + seekbar.value / 100 + ', var(--range-background-color)))';
  398. //volumebar.style.background = '-webkit-gradient( linear, left top, right top, color-stop(' + video.volume + ', var(--range-progress-color)), color-stop(' + video.volume + ', var(--range-background-color)))';
  399. }
  400. })(videos[i])
  401. }
  402. }
  403.  
  404. var stylesheet = `
  405. body.videobody {
  406. height: 100vh;
  407. margin: 0;
  408. padding: 0;
  409. display: flex;
  410. }
  411. videowrapper button {
  412. font-family: "Segoe UI Symbol", system-ui, sans-serif !important;
  413. }
  414. videowrapper label, videowrapper:before {
  415. font-family: system-ui, sans-serif !important;
  416. }
  417. videowrapper {
  418. --background-color: rgba(0, 0, 0, 0.5);
  419. --controls-color: #dcdcdc;
  420. --range-progress-color: #8c8c8c;
  421. --range-background-color: #3c3c3c;
  422.  
  423. display: block;
  424. font-size: 0px;
  425.  
  426. position: relative;
  427. width: auto;
  428. height: auto;
  429. background: inherit;
  430. }
  431. videowrapper.absolute {
  432. display: flex;
  433. position: absolute;
  434. top: 0;
  435. bottom: 0;
  436. left: 0;
  437. right: 0;
  438. margin: auto;
  439. }
  440. .videobody videowrapper.absolute {
  441. position: relative;
  442. }
  443. videowrapper video.iswrapped {
  444. position: relative;
  445. max-height: 100vh;
  446. }
  447. videowrapper:-webkit-full-page-media {
  448. width: auto;
  449. height: auto;
  450. }
  451. videowrapper:-webkit-full-screen {
  452. width: 100%;
  453. height: 100%;
  454. }
  455. videowrapper video::-webkit-media-controls-enclosure {
  456. display:none !important;
  457. }
  458. videowrapper:-webkit-full-screen controls,
  459. videowrapper:-webkit-full-screen header {
  460. z-index: 2147483647;
  461. }
  462.  
  463. videowrapper controls > *,
  464. videowrapper header > * {
  465. color: var(--controls-color);
  466. /* mix-blend-mode: difference; */
  467. background: none;
  468. outline: none;
  469. line-height: 32px;
  470. position: absolute;
  471. font-family: monospace;
  472. }
  473.  
  474. videowrapper controls,
  475. videowrapper header {
  476. overflow: hidden;
  477. white-space: nowrap;
  478.  
  479. transition: all 0.5s ease !important;
  480. background: var(--background-color) !important;
  481. height: 32px !important;
  482. width: 100% !important;
  483. display: block !important;
  484. position: absolute !important;
  485. cursor: default !important;
  486. font-size: 18px !important;
  487. user-select: none;
  488. }
  489. videowrapper controls {
  490. bottom: 0px !important;
  491. }
  492. videowrapper header {
  493. top: 0px !important;
  494. }
  495.  
  496. videowrapper controls,
  497. videowrapper header {
  498. opacity: 1;
  499. }
  500. videowrapper.playing controls,
  501. videowrapper.playing header {
  502. opacity: 0;
  503. }
  504. videowrapper controls:hover, videowrapper.showui controls, videowrapper.paused controls, videowrapper.audiomode controls,
  505. videowrapper header:hover, videowrapper.showui header, videowrapper.paused header, videowrapper.audiomode header {
  506. opacity: 1;
  507. }
  508. videowrapper button, videowrapper label {
  509. line-height: 32px !important;
  510. position: absolute !important;
  511. bottom: 0px !important;
  512. background-color: none !important;
  513. font-size: 18px !important;
  514. height: 32px !important;
  515. border: none !important;
  516. margin: 0 !important;
  517. }
  518. videowrapper button {
  519. width: 32px !important;
  520. padding: 0 !important;
  521. }
  522. videowrapper input[type=range] {
  523. line-height: 32px !important;
  524. position: absolute !important;
  525. background-color: var(--range-background-color) !important;
  526.  
  527. bottom: 10px !important;
  528. height: 10px !important;
  529. border: none !important;
  530. margin: 0px !important;
  531. border-radius: 6px !important;
  532. -webkit-appearance: none !important;
  533. }
  534. videowrapper input[type='range']::-webkit-slider-thumb {
  535. -webkit-appearance: none !important;
  536. background-color: var(--controls-color);
  537. border: none;
  538. height: 16px;
  539. width: 16px;
  540. border-radius: 50%;
  541. }
  542.  
  543. videowrapper.audiomode video:-webkit-full-page-media {
  544. width: 600px;
  545. }
  546. videowrapper:before {
  547. content: attr(data-osd);
  548. position: absolute;
  549. left: 50%;
  550. top: calc(50%);
  551. color: white;
  552. -webkit-text-stroke: 3px #333333;
  553. font-size: 64px;
  554. font-weight: bold;
  555. font-style: normal;
  556. z-index: 1;
  557. transform: translate(-50%, -50%);
  558. pointer-events: none;
  559. opacity: 0;
  560. }
  561. videowrapper.audiomode:before,
  562. videowrapper.showosd:before,
  563. videowrapper.paused:before {
  564. opacity: 1;
  565. }
  566. videowrapper label {
  567. right: 0;
  568. min-width: 100%;
  569. text-align: left;
  570. box-sizing: border-box;
  571. width: 100% !important;
  572. padding: 0px 8px !important;
  573. }
  574. `;
  575.  
  576. function init() {
  577. if (videowrapper_init) {
  578. console.log("Cannot init_videowrapper twice!");
  579. return;
  580. }
  581. videowrapper_init = true;
  582. if (typeof GM_addStyle != "undefined") {
  583. GM_addStyle (stylesheet);
  584. } else {
  585. var css = document.createElement("style");
  586. css.type = "text/css";
  587. css.innerHTML = stylesheet;
  588. document.head.appendChild(css);
  589. }
  590.  
  591. init_videowrapper();
  592. if (typeof unsafeWindow != "undefined") {
  593. unsafeWindow.init_videowrapper = init_videowrapper;
  594. }
  595. }
  596.  
  597. init();