// TimerUI – for home cinemas // == Dependencies == var mediaType; // for compatibility var playerExists = false; // is set to true below if player exists var checkMediaType_enabled = true; // for debugging purposes var media_element = {}; // declaring as object function checkMediaType() { // checks whether it is a video or an audio tag if ( checkMediaType_enabled ) { var mediaTypeBeforeCheck = mediaType; if (document.getElementsByTagName("video")[0]) { playerExists = true; mediaType = "video"; } if (document.getElementsByTagName("audio")[0]) { playerExists = true; mediaType = "audio"; } var mediaTypeAfterCheck = mediaType; if (mediaTypeBeforeCheck != mediaTypeAfterCheck) // Only show media type in console if it has changed. console.log("Detected media type: " + mediaType); media_element = document.getElementsByTagName(mediaType)[0]; // Set back to false if no player is found after using customMediaElement. media_element ? playerExists=true : playerExists=false; } } function customMediaElement(custom_media_element) { checkMediaType_enabled = false; if (custom_media_element) { playerExists = true; media_element = custom_media_element; console.log("customMediaElement: Custom media element set. Reset using checkMediaType_enabled=true."); } else { console.error("customMediaElement: No such media element found."); } } var customTitleElement; function checkFileExtension(ext) { if (typeof(ext) == "string") { ext = ext.toLowerCase(); // case-insensitive // string if (document.location.href.search(new RegExp(ext+"$", "i")) > -1) return true; else return false; } else if (typeof(ext) == "object") { // array – check against multiple strings for (var count=0; count < ext.length; count++) { if (document.location.href.search(new RegExp(ext[count]+"$", "i")) > -1) return true; if (count == ext.length-1) return false; // if no matches after going through them all } } } function isDomain(domain) { // Using .search() instead of .includes() to improve browser compatibility. if (window.location.hostname.search(domain) >= 0) return true; else return false; } // symbols var symbol_play = "▶︎ "; // thin space for alignment var symbol_pause="❚ ❚"; // instead of "⏸" due to Edge browser putting an immutable blue box around it. // mousedown status var mousedown_status; window.addEventListener("mousedown", function(){mousedown_status=true; } ); window.addEventListener("mouseup", function(){mousedown_status=false; } ); function appendChildWithID(tagName,tagID,parent_element) { // default parent element to document.body if unspecified if (parent_element === undefined) parent_element = document.body; parent_element.appendChild(document.createElement(tagName)); // add div parent_element.lastElementChild.id=tagID; // give it ID } function addStyle(new_style,parent_element) { if (parent_element === undefined) parent_element = document.body; parent_element.appendChild(document.createElement("style")); // add style parent_element.lastElementChild.innerHTML = new_style; } // time variables var media_time = {}; // HH:MM:SS timer function HMStimer_core(seconds) { // hours media_time.HH = Math.floor( seconds/3600 ); // leading zero if ( seconds < 36000 ) media_time.HH = "0" + media_time.HH; // minutes media_time.MM = Math.floor( seconds/60%60 ); // leading zero if ( seconds%3600 < 600 ) media_time.MM = "0" + media_time.MM; // seconds media_time.SS = Math.floor( seconds%60 ); // leading zero if ( seconds%60 < 10 ) media_time.SS = "0" + media_time.SS; return media_time.HH+":"+media_time.MM+":"+media_time.SS; } function HMStimer(seconds) { if (seconds >= 0) return HMStimer_core(seconds); // zero or positive if (seconds < 0) // negative { seconds = seconds * (-1); return "-"+HMStimer_core(seconds); } if (seconds == undefined || isNaN(seconds) ) return "– –:– –:– –"; } // MM:SS timer function MStimer_core(seconds) { // minutes media_time.MM = Math.floor( seconds/60 ); // leading zero if ( seconds%3600 < 600 ) media_time.MM = "0" + media_time.MM; // seconds media_time.SS = Math.floor( seconds%60 ); // leading zero if ( seconds%60 < 10 ) media_time.SS = "0" + media_time.SS; return media_time.MM+":"+media_time.SS; } function MStimer(seconds) { if (seconds >= 0) return MStimer_core(seconds); // zero or positive if (seconds < 0) // negative { seconds = seconds * (-1); return "-"+MStimer_core(seconds); } if (seconds == undefined || isNaN(seconds) ) return "– –:– –"; } // implements togglePlay(); – deprecated due to compatibility issues on YouTube (broken site) and Dailymotion ("not a function" error through iframe'd player). /* Object.prototype.togglePlay = function togglePlay() { return this.paused ? this.play() : this.pause(); }; */ // new function without object prototype for compatibility function togglePlay(media_element) { if (media_element) { // validate media element first to avoid errors media_element.paused ? media_element.play() : media_element.pause(); } } // media file extension list var mediafileext = { "video":[".mp4",".mpg",".mpeg",".mts",".mt2s",".m4v",".ts",".ogv",".wmv",".3gp",".3gpp"], "audio":[".mp3",".wma",".wav",".ogg",".opus",".flac",".oga",".wma",".aac",".amr",".alac",".m4a"] }; // == Main code == if (! timerUI) var timerUI = new Object({}); // create parent object if none exists // default system variables timerUI.debug_mode = false; timerUI.override_check = false; timerUI.on = true; timerUI.buffer_on = true; timerUI.multiBuffer = true; // multiple buffer segments timerUI.div = {}; // unset yet, declaring to prevent reference errors timerUI.interval = {}; timerUI.show_remaining = 0; // 0: show elapsed time. 1: show remaining time. 2: show elapsed and total. timerUI.update_during_seek = true; // update timer while dragging seek bar timerUI.color = "rgb(49,136,255)"; // #38F – using RGB for compatibility. timerUI.gradient = "rgba(0,0,0,0.9)"; // using RGBA instead of hexadecimal for compatibility. timerUI.font_pack = "din,futura,'noto sans',ubuntu,'segoe ui',verdana,tahoma,roboto,'roboto light',arial,helvetica,'trebuchet ms',sans-serif,consolas,monospace"; timerUI.width_breakpoint = 768; // pixels // console notifications and warnings (possibly to be expanded) timerUI.msg = { "notimer": "timerUI: No timer found; checking for media element again. Please try again.", "nomedia": "timerUI: no media element found on page. Stopping." }; // text containers (no const for compatibility) var timer_linefeed = "
"; var timer_slash = " / "; // functions timerUI.toggle = {}; timerUI.toggle.main = function() { // show and hide if (timerUI.div) { timerUI.update(); if (timerUI.on) { timerUI.div.style.display = "none"; console.log("timerUI off"); } if (! timerUI.on ) { timerUI.div.style.display = "block"; console.log("timerUI on"); } timerUI.on ? timerUI.on = false : timerUI.on = true; } else { console.warn(timerUI.msg.notimer); timeUI(); } }; timerUI.toggle.buffer = function() { if (timerUI.div) { timerUI.update(); timerUI.updateBufferBar(true); if (timerUI.buffer_on) { timerUI.buffer_bar.style.display = "none"; console.log("timerUI buffer bar off"); } if (! timerUI.buffer_on ) { timerUI.buffer_bar.style.display = "block"; console.log("timerUI buffer bar on"); } timerUI.buffer_on ? timerUI.buffer_on = false : timerUI.buffer_on = true; } else { console.warn(timerUI.msg.notimer); timeUI(); } }; timerUI.toggle.title = function() { if (timerUI.div) { timerUI.update(); timerUI.updateBufferBar(true); if (timerUI.title_on) { timerUI.title.style.display = "none"; console.log("timerUI title off"); } if (! timerUI.title_on ) { timerUI.title.style.display = "block"; console.log("timerUI title on"); } timerUI.title_on ? timerUI.title_on = false : timerUI.title_on = true; } else { console.warn(timerUI.msg.notimer); timeUI(); } }; timerUI.getTitle = function() { timerUI.domainRules(); if (customTitleElement) timerUI.newTitle = customTitleElement.innerHTML; else { // skipping this whole part if no custom title is specified timerUI.newTitle = document.title; timerUI.titleDomainRules(); } if (media_element) { timerUI.updateFileIcon(); return timerUI.fileIcon+" "+timerUI.newTitle; } else { return "TimerUI – designed for home cinemas"; } }; timerUI.guessMediaType = function() { if (isDomain("youtube.com") || isDomain("dailymotion.com") ) return "video"; if (document.location.pathname.search(/^\/video\//) > -1) return "video"; if (checkFileExtension(mediafileext.video) ) return "video"; if (checkFileExtension(mediafileext.audio) ) return "audio"; return "unknown"; // if nothing detected }; timerUI.updateFileIcon = function() { timerUI.fileIcon = timerUI.guessMediaType(); switch(timerUI.fileIcon) { case "video": timerUI.fileIcon = "🎞️"; break; case "audio": timerUI.fileIcon = "♫"; break; case "unknown": timerUI.fileIcon = "📄"; break; } }; timerUI.adaptTitleWidth = function() { if (media_element.duration > 3600 && timerUI.show_remaining == 2 && window.innerWidth > timerUI.width_breakpoint) timerUI.title.style.maxWidth = "calc(100% - 670px)"; else timerUI.title.style.maxWidth = "calc(100% - 400px)"; }; window.addEventListener('resize', function() { if (window.innerWidth < timerUI.width_breakpoint) timerUI.title.removeAttribute("style"); } ); timerUI.update = function() { if (media_element) { timerUI.bar.style.width=media_element.currentTime / media_element.duration * 100 + "%"; // buffer bar update formerly located here; removed from the scope of this function switch(timerUI.show_remaining) { // 0: "HH:MM:SS" 1: "-HH:MM:SS" 2: "MM:SS / MM:SS" or "HH:MM:SS / HH:MM:SS" case 0: timerUI.time.innerHTML=HMStimer(media_element.currentTime); break; case 1: timerUI.time.innerHTML=HMStimer(media_element.currentTime-media_element.duration); break; case 2: if (media_element.duration < 3600 || isNaN(media_element.duration) ) { // show hours if duration exceeds one hour timerUI.time.innerHTML= MStimer(media_element.currentTime) + timer_linefeed + timer_slash + MStimer(media_element.duration); } else { timerUI.time.innerHTML= HMStimer(media_element.currentTime) + timer_linefeed + timer_slash + HMStimer(media_element.duration); } break; } media_element.paused == false ? timerUI.status.innerHTML=symbol_play : timerUI.status.innerHTML=symbol_pause; } else { timerUI.stop(); console.warn(timerUI.msg.nomedia); } }; timerUI.updateTitle = function() { if (timerUI.title) timerUI.title.innerHTML = timerUI.getTitle(); }; // update title on URL change addEventListener('popstate', timerUI.updateTitle); // update title fallback timerUI.interval.updateTitle = setInterval(timerUI.updateTitle, 2000); // buffer bar timerUI.updateBufferBar = function(override_paused) { if (media_element && timerUI.buffer_on && (!media_element.paused || override_paused) ) { timerUI.multiBuffer ? timerUI.update_multi_buffer() : timerUI.single_segment_buffer(); } }; // single-segment buffer bar timerUI.single_segment_buffer = function() { if (timerUI.buffer_bar.innerHTML!="") { timerUI.buffer_bar.style=""; timerUI.buffer_bar.innerHTML=""; } // reset after switching from multi-segment buffer bar // find out first buffer segment after current playback position media_element.buffered.length > 0 ? timerUI.buffer_segment=media_element.buffered.length-1 : timerUI.buffer_segment=0; // media_element.buffered.length is zero until player is initialized // prevent timerUI.buffer_segment from going negative, as it would cause a DOMException error if ( timerUI.buffer_segment > 0) { while (media_element.buffered.end(timerUI.buffer_segment-1) > media_element.currentTime && timerUI.buffer_segment > 1 ) { timerUI.update_single_buffer(); timerUI.buffer_segment-- ; } } }; timerUI.update_single_buffer = function() { if (media_element.buffered.length > 0) { // prevent "DOMException: Index or size is negative or greater than the allowed amount" timerUI.buffer_bar.style.width=media_element.buffered.end(timerUI.buffer_segment) / media_element.duration * 100 + "%"; } else { timerUI.buffer_bar.style.width="0%"; } }; // multi-segment buffer bar – highlight all buffered parts timerUI.update_multi_buffer = function() { if (timerUI.buffer_bar.style.length < 1) { timerUI.buffer_bar.style.width="100%"; timerUI.buffer_bar.style.backgroundColor="rgba(0,0,0,0)"; } if (media_element.buffered.length > 0) { timerUI.generate_buffer_segments(); } else { timerUI.buffer_bar.style.width="0%"; } }; timerUI.generate_buffer_segments = function() { timerUI.buffer_bar.innerHTML=""; // reset to re-generate segments for (count=0; count < media_element.buffered.length; count++) { timerUI.append_buffer_segment(timerUI.get_buffer_range(count) ); } timerUI.select_segments = timerUI.buffer_bar.getElementsByClassName("timerUI_buffer_segment"); timerUI.segment_count = timerUI.select_segments.length; }; timerUI.append_buffer_segment = function(start_pos,end_pos) { if (!start_pos) start_pos=timerUI.get_buffer_range(segment_number).start_pos; if (!end_pos) end_pos=timerUI.get_buffer_range(segment_number).end_pos; timerUI.buffer_bar.appendChild(document.createElement("div") ); timerUI.buffer_bar.lastElementChild.classList.add("timerUI_buffer_segment"); timerUI.buffer_bar.lastElementChild.style="left:"+start_pos+"%;width:"+(end_pos-start_pos)+"%;background-color:"+timerUI.color+";"; }; timerUI.get_buffer_range = function(segment_number) { return { start_pos: media_element.buffered.start(segment_number) / media_element.duration * 100, end_pos: media_element.buffered.end(segment_number) / media_element.duration * 100 }; // object with start and end percentages }; timerUI.set_buffer_segment = function(segment_number,start_pos,end_pos) { var selection=timerUI.buffer_bar.getElementsByClassName("timerUI_buffer_segment"); selection[segment_number].style.left = start_pos / media_element.duration * 100 + "%"; selection[segment_number].style.width = (end_pos-start_pos) / media_element.duration * 100 + "%"; }; // colors timerUI.setColor = function(newColor) { newColor == "default" ? timerUI.color="rgb(49,136,255)" /* #38F */ : timerUI.color = newColor; timerUI.bar.style.backgroundColor=timerUI.color; timerUI.buffer_bar.style.backgroundColor=timerUI.color; timerUI.bar.style.boxShadow="0 0 30px 0 "+timerUI.color; // (deprecated due to new buffer bar) timerUI.bar_placeholder.style.backgroundColor=timerUI.color; timerUI.time.style.color=timerUI.color; timerUI.status.style.color=timerUI.color; timerUI.title.style.color=timerUI.color; }; timerUI.setGradient = function(newGradient) { newGradient == "default" ? timerUI.gradient="rgba(0,0,0,0.9)" : timerUI.gradient = newGradient; timerUI.gradient_placeholder.style.backgroundImage="linear-gradient(to top,"+timerUI.gradient+", rgba(0,0,0,0) )"; }; timerUI.setFont = function(newFont) { timerUI.time.style.fontFamily=newFont; timerUI.title.style.fontFamily=newFont; }; timerUI.stop = function() { timerUI.status.innerHTML="■"; timerUI.bar.style.width=0; timerUI.buffer_bar.style.width=0; timerUI.time.innerHTML=HMStimer(undefined); }; // Additional checks to ensure the player is detected window.onclick = function() { checkMediaType();timeUI(); }; window.addEventListener("keydown", function() { checkMediaType();timeUI(); } ); function timeUI() { // slightly different name to prevent naming collision with timerUI object checkMediaType(); timerUI.domainRules(); // load domain rules // add timerUI if it does not already exist if ( ( ! document.getElementById("timerUI") ) && playerExists || timerUI.override_check ) { // Adding elements // parent element appendChildWithID("div","timerUI"); timerUI.div = document.getElementById("timerUI"); // button styling addStyle("#timerUI button { background:none; border:none; outline:none; line-height:unset; } #timerUI button:hover { filter: brightness(1.2); } #timerUI button:active { filter: brightness(0.6); }", timerUI.div); // to suppress button background and border on earlier browser versions timerUI.div.lastElementChild.classList.add("timerUI_buttons"); // label to improve visibility in page inspector // background gradient appendChildWithID("div","timerUI_bottom_gradient",timerUI.div ); timerUI.gradient_placeholder = document.getElementById("timerUI_bottom_gradient"); addStyle("#timerUI #timerUI_bottom_gradient { display:block; position:fixed; background-image:linear-gradient(to top,"+timerUI.gradient+", rgba(0,0,0,0) ); opacity:1; height:80pt; width:100%; bottom:0; pointer-events:none; }", timerUI.div); // progress bar appendChildWithID("div","timerUI_progress_bar",timerUI.div ); timerUI.bar = document.getElementById("timerUI_progress_bar"); addStyle("#timerUI #timerUI_progress_bar { display:block; position:fixed; background-color:"+timerUI.color+"; box-shadow: 0 0 30px 0px "+timerUI.color+"; height:8pt; width:50%; bottom:0; }", timerUI.div); // buffer bar appendChildWithID("div","timerUI_buffer_bar",timerUI.div ); timerUI.buffer_bar = document.getElementById("timerUI_buffer_bar"); addStyle("#timerUI #timerUI_buffer_bar, #timerUI .timerUI_buffer_segment { display:block; position:fixed; background-color:"+timerUI.color+"; height:8pt; width:75%; bottom:0; opacity:0.4; } #timerUI .timerUI_buffer_segment { opacity:1; }", timerUI.div); // media title appendChildWithID("div","timerUI_media_title",timerUI.div ); timerUI.title = document.getElementById("timerUI_media_title"); timerUI.title.innerHTML = timerUI.getTitle(); addStyle("#timerUI #timerUI_media_title { position:fixed; text-shadow: 0 0 5px black; display:inline; display:-webkit-box; bottom:15pt; left:2pt; color:"+timerUI.color+"; font-family:"+timerUI.font_pack+"; font-size:20pt; width:60%; max-width:calc(100% - 500px); text-overflow: ellipsis; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; vertical-align: bottom;"); // timer appendChildWithID("button","timerUI_media_timer",timerUI.div ); timerUI.time = document.getElementById("timerUI_media_timer"); timerUI.time.innerHTML="00:00:00"; addStyle("#timerUI #timerUI_media_timer { display:block; color:"+timerUI.color+"; position:fixed; cursor:pointer; font-size:50pt; text-shadow: 0 0 20px black; line-height:60pt; bottom:10pt; right:30pt; text-align:right; font-weight:400; font-family:"+timerUI.font_pack+"; } #timerUI #timerUI_media_timer .timer_linefeed { display:none; }", timerUI.div); // play/pause symbol appendChildWithID("button","timerUI_playback_status",timerUI.div ); timerUI.status = document.getElementById("timerUI_playback_status"); timerUI.status.innerHTML="■"; addStyle("#timerUI #timerUI_playback_status { display:block; position:fixed; cursor:pointer; color:"+timerUI.color+"; font-size:24pt; line-height:40pt; bottom:30pt; right:3pt; font-family:none; }", timerUI.div); // progress bar placeholder – put last to be at the top in stacking context appendChildWithID("div","timerUI_progress_placeholder",timerUI.div ); timerUI.bar_placeholder = document.getElementById("timerUI_progress_placeholder"); addStyle("#timerUI #timerUI_progress_placeholder { display:block; position:fixed; cursor:pointer; background-color:grey; height:8pt; width:100%; bottom:0; opacity:0.2; }", timerUI.div); // responsive - at bottom to be able to override CSS properties without !important flag. addStyle("@media (max-width:"+timerUI.width_breakpoint+"px) { #timerUI #timerUI_media_timer { font-size:30pt; line-height:24pt; bottom:15pt; } #timerUI #timerUI_playback_status { bottom:10pt; } } @media (max-width:500px) { #timerUI #timerUI_media_timer .timer_linefeed { display:inline; } #timerUI #timerUI_media_timer .timer_slash { display:none; } } @media (max-width:"+timerUI.width_breakpoint+"px) { #timerUI #timerUI_buffer_bar { font-size:10pt; -webkit-line-clamp: 3; max-width:60%;} } @media (max-width:480px) { #timerUI #timerUI_buffer_bar { display: none; } } ", timerUI.div); timerUI.div.lastElementChild.classList.add("timerUI_responsive"); // update timer during playback every fifteenth of a second and while mouse is dragging progress bar timerUI.interval.update = setInterval( function() { if ( ( media_element /* exists? */ && timerUI.update_during_seek && mousedown_status ) || ( media_element && timerUI.on && ! media_element.paused ) ) timerUI.update(); }, 1000/15 ); // Longer interval for buffer bar to minimize CPU usage timerUI.interval.buffer = setInterval(timerUI.updateBufferBar, 1000); // play and pause toggle timerUI.status.onclick = function() { togglePlay(media_element); timerUI.update(); timerUI.updateBufferBar(true); }; // former code with object prototype caused compatibility issues on various sites: media_element.togglePlay(); // toggle between elapsed, remaining, and elapsed/total time timerUI.time.onclick = function() { switch(timerUI.show_remaining) { case 0: timerUI.show_remaining = 1; break; case 1: timerUI.show_remaining = 2; timerUI.adaptTitleWidth(); break; case 2: timerUI.show_remaining = 0; timerUI.adaptTitleWidth(); break; } timerUI.update(); timerUI.updateBufferBar(true); }; // clickable progress bar (experimental) - no "timerUI." notation because inside main function timerUI.clickSeekBar = function(m){ if (media_element) { var clickPos = m.clientX / timerUI.bar_placeholder.offsetWidth; // go to beginning if clicked in first percentile if (clickPos < 0.01 ) media_element.currentTime = 0; else media_element.currentTime = media_element.duration * clickPos; timerUI.update(); timerUI.updateBufferBar(true); } }; function dragSeekBar(m){ // currently unused m = m || window.event; var clickPos = m.pageX / timerUI.bar_placeholder.offsetWidth; var dragSeekBarInterval = setInterval( function() { media_element.currentTime = media_element.duration * clickPos; timerUI.update(); if (! mousedown_status) { clearInterval(dragSeekBarInterval); return; } }, 200); } timerUI.bar_placeholder.addEventListener("mousedown", timerUI.clickSeekBar ); // (incomplete) timerUI.bar_placeholder.addEventListener("mousemove", dragSeekBar ); // (obsolete) timerUI.bar_placeholder.addEventListener("mouseup", clickSeekBar ); timerUI.update(); // == Patches == // prevent missing out on pausing from inside a site's existing player window.addEventListener("mouseup", function() { setTimeout(timerUI.update, 200); } ); window.addEventListener("keyup", function() { setTimeout(timerUI.update, 200); } ); // prevent detaching from player on sites with playlists such as Internet Archive timerUI.interval.checkMedia = setInterval( checkMediaType,1000 ); // prevent indicating "▶" after playback finished timerUI.interval.checkPaused = setInterval( function() { if ( media_element /* exists? */ && media_element.paused && ! timerUI.pause_checked) { timerUI.update(); timerUI.pause_checked = true; if (timerUI.debug_mode) console.debug("timerUI: checking paused status: "+media_element.paused); } else if ( media_element && ! media_element.paused ) { timerUI.pause_checked = false; } // to avoid redundant checks while paused },1000 ); } else { // warn in console that no player exists; prevent repetition if (! playerExists && ! timerUI.noMediaWarned) { console.warn(timerUI.msg.nomedia); timerUI.noMediaWarned = true; } } } // == Custom domain rules == timerUI.domainRules = function() { if (isDomain("dailymotion.com") && document.location.pathname.search(/^\/embed\//) < 0 ) { // Dailymotion watch page, excluding embed page. customMediaElement(document.getElementById("player-body").contentWindow.document.getElementsByTagName("video")[0]); customTitleElement = document.getElementById("media-title"); // for unlisted } }; timerUI.titleDomainRules = function() { // custom domain rules for title if ( isDomain("youtube.com") || isDomain("dailymotion.com") || isDomain("wikimedia.org") || isDomain("wikipedia.org") || isDomain("wikiversity.org") || isDomain("wikibooks.org") || isDomain("mediawiki.org") ) { // negative lookahead regular expression – trim after last dash // Match both normal dash "-" and ndash "–", since the German-language wikis use the latter. timerUI.newTitle = decodeURI(timerUI.newTitle.substring(0,timerUI.newTitle.search(/(-|–)(?:.(?!(-|–)))+$/)-1 ) ); } // remove "File:" prefix on wikis if ( isDomain("wiki") ) { if (document.title.search(/^File:/) == 0 ) timerUI.newTitle = timerUI.newTitle.substring(5); if (document.title.search(/^Datei:/) == 0 ) timerUI.newTitle = timerUI.newTitle.substring(6); } if ( isDomain("archive.org") && ! isDomain("web.archive.org") ) { timerUI.newTitle = (document.location.href+"").substring((document.location.href+" ").search(/\/(?:.(?!\/))+\/?$/)+1 ); // after last slash (additional space prevents full URL from being matched) if (timerUI.newTitle != "") { timerUI.newTitle += " - "; } timerUI.newTitle += document.title.substring(0,document.title.search(/:(?:.(?!:))+(?:.(?!:))+/)-1 ); // trim after second-last colon, -1 to remove space at end decodeURI(timerUI.newTitle); // prevent spaces from turning into "%20". } }; // == Master function == timeUI();