LooseStool

videoJump.js - BOOKMARKLET to easily jump to Time Code like "1h20s" (esp. helpful for MOBILE)

Jul 29th, 2022 (edited)
888
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // javascript:void function(){"use strict";(function(a){let b=function(a){return new Date(1e3*+(a=0>a?0:a||0)||0).toISOString().slice(11,19+(a===(0|a)?0:4)).split(":").map((b,c)=>+b||!a&&1<c?`${+b}${c?1<c?"s":"m":"h"}`:"").join("")},c=function(a){return null!=a&&(a=(a+"").replace(/[^hms.\d]/g,"")).length?(a.replace(/^[^s]*[.\d]+$/,"$&s").match(/(?:([.\d]+)h)?(?:([.\d]+)m)?(?:([.\d]+)s)?/)||[]).slice(1).reduce((a,b,c)=>a+(b*Math.pow(60,2-c)||0),0):null},d="unauthorized",e="youtube",f=function(a){let b=a&&`${new URL(a).hostname}:`.split(":")[0]||"";switch([`${d}.tv`,`${e}.com`].findIndex(function(a){return b.endsWith(a)})){case 0:return"string"==typeof urlProtocol&&d;case 1:return e;}},g=function(a){switch(f(a)){case d:{let b=a&&new URL(a),c="object"==typeof uatvPlayer&&uatvPlayer,d=c&&contentId,e=d&&c.currentTime(),f=b&&b.pathname.match(/\b((watch|video)|(listen))\b/)||[],g=f[2]?"/watch":f[3]?"/listen":null;return g&&[d,e,g]}case e:{let b=a&&new URL(a),c=b&&(b.searchParams.get("v")||(b.pathname.match(/^\/(?:shorts|video|watch|live)\/([^\/?&]+)/)||[])[1]),d=c&&document.querySelector("video"),e=d&&d.currentTime;return d&&[c,e,d&&"/watch",(b.search.match(/(&list=[^&]+)(&index=\d+)?/)||[])[0]]}}},h=function(a,c,g,h,i){switch(f(a)){case d:return`${urlProtocol}//${urlDomain}${g}/${c}/s${h}/`;case e:return`${a.origin}${g}?v=${c}${i||""}&t=${b(h)}`;}},i=", ; : ' -";return function(a,d){let e=window.location,[j,k,l,m]=g(e)||[],n=b(null==a?k||0:0>a?0:a),o=`\n${f(e)}${l} &t = ##h ##m ##s\n\nTo open in NEW window, include any of these characters: ${i}\n`,p=`\nTo open in NEW window, include any of these characters: ${i}\n\n${f(e)}${l} &t = ##h ##m ##s\n`,q=l?prompt(o,`t=${n}`):null,r=RegExp("["+i.replace(/ /,"")+"]").test(q+""),s=c(q),t=l&&h(e,j,l,s,m);console.log("%c%s%c\tjumpToTimecode(prompt default: %c%s%c) => [newSeconds = %c%s%c]\n%o%s","color:green",new Date,"","color:blue",n,"","color:red;font-weight:700",s,"",{redirectTo:t},null==s?" (aborted)":""),t&&null!=s&&!d&&setTimeout(function(){r?window.open(t):e.assign(t)},2000)}(a)})()}();
  2.  
  3. // videoJump.js - BOOKMARKLET to easily jump to Time Code like "1h20s" (esp. helpful for MOBILE)
  4. // https://pastebin.com/n2635gsi pasted by LooseStool (https://libertylinks.io/LooseStool) on 29Jul2022 @ 111pm
  5.  
  6. // e.g. https://unauthorized.tv/watch/483471/s5060/ or https://unauthorized.tv/video/483471/s5060/
  7.  
  8. // 27Apr2023: BUGFIX YouTube '/live/id' URLs; timecodeToSeconds (and secondsToTimecode) allows '3.6' not just '3.0' AND will auto-suffix 's' if ending with just #; '.' removed from CHARS_OPEN_IN_NEW_WINDOW
  9.  
  10. // 03Sep2022: BUGFIX in getVideoInfo() so now actually works for YouTube videos using these URLs: /shorts/id and /video/id and /watch/id ( instead of the normal /watch?v=id )
  11. // 14Aug2022: inside the prompt() you can enter an extra character ( any of , . ; : ' - )  to say "open in new window".
  12.  
  13. // Also, now it works not just with UATV but with YOUTUBE too -- including /shorts/ and inside playlists.
  14. // https://www.youtube.com/watch?v=ufHIk5W_JBo&list=PLVuv8qtX5I89QNpuK5xI2tFaK2I0Pa43z&index=1&t=61s
  15. // https://www.youtube.com/watch?v=ufHIk5W_JBo&list=PLVuv8qtX5I89QNpuK5xI2tFaK2I0Pa43z&t=61s
  16. // https://www.youtube.com/watch?v=ufHIk5W_JBo&t=61s
  17. // https://www.youtube.com/shorts/4oe-Cy_dIQk
  18. // https://www.youtube.com/watch/4oe-Cy_dIQk
  19. // https://www.youtube.com/video/4oe-Cy_dIQk
  20. // and, of course, mobile URLs too: https://m.youtube.com/watch?v=ufHIk5W_JBo&list=PLVuv8qtX5I89QNpuK5xI2tFaK2I0Pa43z&index=1&t=61s
  21. // https://m.youtube.com/watch?v=ufHIk5W_JBo&list=PLVuv8qtX5I89QNpuK5xI2tFaK2I0Pa43z&t=61s
  22. // https://m.youtube.com/watch?v=ufHIk5W_JBo&t=61s
  23. // https://m.youtube.com/shorts/4oe-Cy_dIQk
  24. // https://m.youtube.com/watch/4oe-Cy_dIQk
  25. // https://m.youtube.com/video/4oe-Cy_dIQk
  26.  
  27.  
  28.  
  29. "use strict";
  30. let videoJump = function(secondsDefault) {
  31.  
  32.     // returns timecode string "##h##m##s" from int seconds ("0s" if fails); using Date(1000 * seconds).toISOString()...map() allows us to remove "0h" or "0m" or "0s", unlike this: `${seconds = Number(seconds), Math.floor(seconds / 3600)}h${Math.floor(seconds % 3600 / 60)}m${Math.floor(seconds % 3600 % 60)}s`
  33.     let secondsToTimecode = function(seconds) {
  34.         // return ( new Date( 1000 * Number( seconds = (seconds < 0 ? 0 : seconds | 0 ) ) || 0 ) )
  35.         return (
  36.             new Date(
  37.                 1000 * Number(
  38.                     seconds = ( seconds < 0 ? 0 : seconds || 0 )
  39.                 ) || 0
  40.             )
  41.         )   // || 0 instead of | 0 -- because why not allow for "3.6s" :-)
  42.         // .toISOString().slice(11, 19).split(":")  // oh yeah THIS is "why not": (3) ['00', '00', '03'] instead of (3) ['00', '00', '03.6']
  43.         .toISOString()
  44.         .slice(11, 19 + (
  45.             seconds === (seconds | 0) ? 0 : 4
  46.         ) ) // ^ so (3.6) => ['00', '00', '03.600'] , but (3.0) => ['00', '00', '03']
  47.         .split(":")
  48.         .map(
  49.             (n, i) => (
  50.                 +n || (!seconds && i > 1)
  51.                 ? `${+n}${i ? i > 1 ? "s" : "m" : "h"}`
  52.                 : ""
  53.             )
  54.         )
  55.         .join("");
  56.     }
  57.  
  58.     // returns int seconds from timecode string (null if fails); very forgiving when parsing by first removing everything except [0..9, h, m, s]
  59.     , timecodeToSeconds = function(string) {
  60.         return string == null || !(
  61.             string = String(string)
  62.             .replace(/[^hms.\d]/g, "")  // mainly to remove "t=" prefix, and any spaces etc. But makes sense to nuke ALL except h m s . \d
  63.         ).length
  64.         ? null
  65.         : (
  66.             string
  67.             .replace(/^[^s]*[.\d]+$/, "$&s")
  68.             // ^ convenience syntax allowed: ending with just "###" = presumed "###s" (unless already "s")
  69.             // so "  3  6  " => "36s" = 36, but "  3  s  6  s  " => "3s6s" = 3, "  3  s  6  " => "3s6" = 3, " 3 . 6 m " => 216, and "  " => "" = 0
  70.             .match(/(?:([.\d]+)h)?(?:([.\d]+)m)?(?:([.\d]+)s)?/)
  71.             || []
  72.         ).slice(1)
  73.         .reduce(
  74.             (c, n, i) => (
  75.                 c + (
  76.                     n * Math.pow(60, 2 - i)
  77.                     || 0
  78.                 )
  79.             ),
  80.             0
  81.         );
  82.     }
  83.  
  84.     , UA = "unauthorized"
  85.  
  86.     , YT = "youtube"
  87.  
  88.     , getSite = function(loc) {
  89.         let hostname = loc && `${new URL(loc).hostname}:`.split(":")[0] || "";
  90.         switch( [`${UA}.tv`, `${YT}.com`].findIndex( function(el){ return hostname.endsWith(el); } ) ) {
  91.             case 0: return typeof urlProtocol === "string" && UA;
  92.             case 1: return YT;
  93.         };
  94.     }
  95.  
  96.     , getVideoInfo = function(loc) {    // [videoId, currentTime, action, playlistInfo] = getVideoInfo(loc) || []
  97.         switch(getSite(loc)) {
  98.             case UA: {
  99.                 let url = loc && new URL(loc)
  100.                 , video = typeof uatvPlayer === "object" && uatvPlayer
  101.  
  102.                 , videoId = video && contentId
  103.  
  104.                 , currentTime = videoId && video.currentTime()
  105.  
  106.                 , matches = url && url.pathname.match(/\b((watch|video)|(listen))\b/) || []
  107.                 , action = matches[2] ? "/watch" : matches[3] ? "/listen" : null;
  108.  
  109.                 return action && [ videoId, currentTime, action ];
  110.             }
  111.             case YT: {
  112.                 // (for debugging) let url = loc && new URL(loc.href.replace("www.you", "m.you").replace(/\/(shorts|video|watch)\//g, "/watch/"))
  113.                 let url = loc && new URL(loc)
  114.  
  115.                 , videoId = url && ( url.searchParams.get("v") || (url.pathname.match(/^\/(?:shorts|video|watch|live)\/([^\/?&]+)/) || [])[1] )
  116.                 // test case:   https://m.youtube.com/live/CphmtP0LTek instead of https://m.youtube.com/watch?v=CphmtP0LTek
  117.                 // FAIL:    , videoId = url && ( url.searchParams.get("v") || (url.pathname.match(/^\/(?:shorts|video|watch)\/([^\/?&]+)/) || [])[1] )
  118.                 // ^ WOW :blink: @ t=27m39s
  119.  
  120.                 , video = videoId && document.querySelector("video")
  121.                 , currentTime = video && video.currentTime
  122.  
  123.                 , action = video && "/watch";
  124.  
  125.                 return video && [ videoId, currentTime, action, (url.search.match(/(&list=[^&]+)(&index=\d+)?/) || [])[0] ];
  126.             }
  127.         };
  128.     }
  129.  
  130.     , getRedirectTo = function(loc, videoId, action, newSeconds, playlistInfo) {
  131.         switch(getSite(loc)) {
  132.             case UA: return `${urlProtocol}//${urlDomain}${action}/${videoId}/s${newSeconds}/`;
  133.             case YT: return `${loc.origin}${action}?v=${videoId}${playlistInfo || ""}&t=${secondsToTimecode(newSeconds)}`;
  134.         };
  135.     }
  136.  
  137.     // , CHARS_OPEN_IN_NEW_WINDOW = ", . ; : ' -"
  138.     , CHARS_OPEN_IN_NEW_WINDOW = ", ; : ' -"
  139.  
  140.     // always prompts (unless no 'action') and will abort if timecodeToSeconds() fails (which includes when Cancel/Escape is pressed)
  141.     , jumpToTimecode = function(seconds, debug_LogInsteadOfRedirecting) {
  142.         let loc = window.location
  143.         , [videoId, currentTime, action, playlistInfo] = getVideoInfo(loc) || []
  144.         , def = secondsToTimecode( seconds == null ? currentTime || 0 : seconds < 0 ? 0 : seconds )
  145.  
  146.         , msg = `\n${getSite(loc)}${action} &t = ##h ##m ##s\n\nTo open in NEW window, include any of these characters: ${CHARS_OPEN_IN_NEW_WINDOW}\n`
  147.         , msg2 = `\nTo open in NEW window, include any of these characters: ${CHARS_OPEN_IN_NEW_WINDOW}\n\n${getSite(loc)}${action} &t = ##h ##m ##s\n`
  148.         , newPlayerTime = action ? prompt(msg, `t=${def}`) : null
  149.  
  150.         , shouldOpenInNewWindow = RegExp("[" + CHARS_OPEN_IN_NEW_WINDOW.replace(/ /, "") + "]").test(String(newPlayerTime))
  151.         , newSeconds = timecodeToSeconds(newPlayerTime)
  152.  
  153.         , redirectTo = action && getRedirectTo(loc, videoId, action, newSeconds, playlistInfo)
  154.         , actuallyRedirect = redirectTo && newSeconds != null && !debug_LogInsteadOfRedirecting
  155.         , REDIRECT_DELAY_MS = 2000;
  156.  
  157.         console.log("%c%s%c\tjumpToTimecode(prompt default: %c%s%c) => [newSeconds = %c%s%c]\n%o%s"
  158.             , "color:green", new Date(), ""
  159.             , "color:blue", def, ""
  160.             , "color:red;font-weight:700", newSeconds, ""
  161.             , {redirectTo}
  162.             , newSeconds != null ? "" : " (aborted)"
  163.         );
  164.  
  165.         actuallyRedirect && setTimeout(
  166.             function() {
  167.                 shouldOpenInNewWindow
  168.                 ? window.open(redirectTo)
  169.                 : loc.assign(redirectTo);
  170.             },
  171.             REDIRECT_DELAY_MS
  172.         );
  173.     };
  174.  
  175.     // return jumpToTimecode(secondsDefault, true); // (for debugging)
  176.     return jumpToTimecode(secondsDefault);
  177.  
  178. };
  179.  
  180. videoJump();
  181.  
  182.  
  183.  
  184. /*
  185.  
  186. https://social.infogalactic.com/micropost/b42c0b5e-e652-41f9-8973-0f3320f1b87b
  187.  
  188. Loose Stool😶 @LooseStool     a day ago
  189. For UATV videos (and audio) what is the URL syntax for jumping to a particular part — the equivalent of
  190.  
  191. unauthorized.tv/watch/483471/?t=1h24m20s
  192.  
  193. ^ Also, UATV DEVS please make the server parsing translate this syntax too!
  194.  
  195.  
  196. The Gamma | Owen's Livestreams #1454 | Unauthorized.TV
  197. The Gamma
  198. unauthorized.tv
  199.  
  200. Roescoe @Roescoe     a day ago
  201. unauthorized.tv/watch/483471/s5060/
  202.  
  203.  
  204. The Gamma | Owen's Livestreams #1454 | Unauthorized.TV
  205. The Gamma
  206. unauthorized.tv
  207.  
  208. Loose Stool😶 @LooseStool     a day ago
  209. Ah, it is /s### not /s/### (I forgot)
  210.  
  211. But 5060 seconds is not easy math to do when casually viewing :(
  212.  
  213. Too bad the convenient link seems to not be visible on mobile.
  214.  
  215. Or at least allow parsing alias for: /h124m20s if the ?t ain’t possible
  216.  
  217.  
  218. Roescoe @Roescoe     a day ago
  219. It only shows when you switch to audio and then back or vice versa. It’s not a fully fledged implementation yet. I assume eventually there will be shareable links and saved spots.
  220.  
  221.  
  222. Loose Stool😶 @LooseStool     a day ago
  223. I noticed if you go cold it doesnt work, but if already playing then it works. Which means refreshing makes it work that second time (after reload).
  224.  
  225. Maybe today I will throw together a quick bookmarklet as a temporary workaround for myself
  226.  
  227. */
  228.  
Advertisement
Add Comment
Please, Sign In to add comment