From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.

// == 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

var tmp="",count=0; // initiating temporary variables used inside functions and loops



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(extcount+"$", "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 media_symbol = {};

	media_symbol.play = "▶&#xFE0E;&thinsp;"; // thin space for alignment

	media_symbol.pause="&#10074;&thinsp;&#10074;"; // instead of "⏸" due to Edge browser putting an immutable blue box around it.

	media_symbol.stop="■";



// 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 "–&thinsp;–:–&thinsp;–:–&thinsp;–";



}



// 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 "–&thinsp;–:–&thinsp;–";



}





// 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", ".webm"],

	"audio":".mp3", ".wma", ".wav", ".ogg", ".opus", ".flac", ".oga", ".wma", ".aac", ".amr", ".alac", ".m4a"

};



// "replaceAll()" polyfill for pre-2020 browsers

	// based on: https://stackoverflow.com/a/1144788 

		

function escapeRegExp(string) {

	return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string

}



	// renamed function to prevent interference with "replaceAll()" on browsers since 2020

function replaceAll_polyfill(str, find, replacement) {

	return str.replace(new RegExp(escapeRegExp(find), 'g'), replacement);

}



// == 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.default_color = "rgb(49,136,255)"; // memorize default color

timerUI.gradient = "rgba(0,0,0,0.9)"; // using RGBA instead of hexadecimal for compatibility.

timerUI.font_pack = "din, futura, 'noto sans', 'open sans', ubuntu, 'segoe ui', verdana, tahoma, roboto, 'roboto light', arial, helvetica, 'trebuchet ms' ,'bitstream vera sans', 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 = "<span class=timer_linefeed><br /></span>";

var timer_slash = " <span class=timer_slash>/</span> ";



	// 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");

		}

		if (timerUI.on) { timerUI.on = false; return false; } else { timerUI.on = true; return false; }

	} 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");

		}

		if (timerUI.buffer_on) {

			timerUI.buffer_on = false; return false;

		} else {

			timerUI.buffer_on = true; return 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");

		}

		if (timerUI.title_on) {

			timerUI.title_on = false; return false;

		} else {

			timerUI.title_on = true; return true;

		}

	} else {

		console.warn(timerUI.msg.notimer);

		timeUI();

	}

};



timerUI.getTitle = function() { 

	if (! timerUI.domainRules_checked) /* only check domain rules once */ {

		timerUI.domainRules();

		timerUI.domainRules_checked = true;

	}

	if (customTitleElement) timerUI.newTitle = customTitleElement.innerHTML;

	else { // skipping this whole part if no custom title is specified

		timerUI.newTitle = document.title;

		// replace underscores with spaces

		timerUI.newTitle = replaceAll_polyfill(timerUI.newTitle, "_"," ");

		timerUI.titleDomainRules();

	}

	if (media_element) {

		timerUI.updateFileIcon();

		return timerUI.file_icon+"&nbsp;"+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 (! media_element.videoWidth) return "audio"; // Detects files that only contain audio, even if they have a video file extension.

    if (media_element.videoWidth > 0) return "video";

	if (checkFileExtension(mediafileext.video) ) return "video";

	if (checkFileExtension(mediafileext.audio) ) return "audio";

	return "unknown"; // if nothing detected

};



timerUI.updateFileIcon = function() {

	timerUI.file_icon = timerUI.guessMediaType();

	switch(timerUI.file_icon) {

		case "video": timerUI.file_icon = "🎞️"; break;

		case "audio": timerUI.file_icon = "♫"; break;

		case "unknown": timerUI.file_icon = "📄"; 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;

		}



		if (media_element.paused) {

			timerUI.status.innerHTML=media_symbol.pause;

		} else {

			timerUI.status.innerHTML=media_symbol.play;

		}

	} 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).start_pos,

			timerUI.get_buffer_range(count).end_pos

		);

	}

	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) {

	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");

	selectionsegment_number].style.left = start_pos / media_element.duration * 100 + "%";

	selectionsegment_number].style.width = (end_pos-start_pos) / media_element.duration * 100 + "%";

};







// colors

timerUI.setColor = function(newColor) {

	if (! timerUI.bar) {

		/* prevent running function before timerUI is properly loaded into the DOM to prevent exception */

		if (timerUI.debug_mode) console.debug("timerUI: setColor can not run before timerUI is properly loaded.");

		return false;

	}

	timerUI.previous_color = timerUI.color; // memorize previous setting

	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;



	// colour all buffer segments

	for (var count=0; count < timerUI.buffer_bar.childNodes.length; count++) {

		timerUI.buffer_bar.childNodescount].style.backgroundColor=timerUI.color;

	}

	if (timerUI.debug_mode) console.debug("timerUI: color changed from "+timerUI.previous_color+" to "+timerUI.color);

};



timerUI.setColor("default"); // prevent mixed colours if the code is run multiple times



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;

};





// light mode



timerUI.light_mode = false; // default



timerUI.light_mode_on = function() {

	timerUI.light_mode = true;

	timerUI.color_before_light_mode = timerUI.color;

	// improves visibility:

	if (timerUI.setColor) /* exists*/ {

		timerUI.setColor("lightblue");

	} else { return false; /* error */ }

	if (timerUI.gradient_placeholder) /* exists*/ {

		timerUI.gradient_placeholder.style.backgroundColor="rgba(0, 0, 0, 0.5)";

	} else { return false; /* error */ }

};



timerUI.light_mode_off = function() {

	timerUI.light_mode = false;

	if (timerUI.setColor) /* exists*/ {

		timerUI.setColor("lightblue");

	} else { return false; /* error */ }

	if (timerUI.gradient_placeholder) /* exists*/ {

		timerUI.gradient_placeholder.style.backgroundColor=""; // fall back to default

	} else { return false; /* error */ }

};



timerUI.toggle.light_mode = function() {

	if ( ! timerUI.light_mode ) /* if light mode is deactivated */ {

		timerUI.light_mode_on();

		return true;

	} else {

		timerUI.light_mode_off();

		return false;

	}

};



// "stop" icon



timerUI.stop = function() {

	timerUI.status.innerHTML=media_symbol.stop;

	timerUI.bar.style.width=0;

	timerUI.buffer_bar.style.width=0;

	// appearance of stopped timer consistent with the show_remaining setting

	if (timerUI.show_remaining == 2) {

		timerUI.time.innerHTML=MStimer(undefined)+" / "+MStimer(undefined);

	} else {

		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();

	// 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; padding:0; margin:0; }  #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%; left:0; bottom:0; pointer-events: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

		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%; left:0; 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%; left:0; bottom:0; opacity:0.4; } #timerUI .timerUI_buffer_segment { opacity:1; }", timerUI.div);





		// 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);







		// 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%; left:0; 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");



		// 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;", timerUI.div);



		timerUI.domainRules(); // load domain rules after initializing timer elements



	// 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) - "clickPos" has no "timerUI." notation because it is inside the 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 videos (Dailymotion only displays the video title in the HTML page title for public videos)

	}



	// activate light mode on wikis and Dailymotion due to bright backgrounds

	if ( isDomain("wiki") 

		|| isDomain("dailymotion.com") 

		|| isDomain("ghostarchive.org") 

		|| is_archive_library() ) {

		timerUI.light_mode_on();

	}



	// media embedded on Wayback Machine

	if ( is_WaybackEmbed() ) {

		tmp = document.getElementsByTagName("iframe")[0]; // iframe in temporary variable to deduplicate code

		customMediaElement(

			tmp.contentWindow.document.getElementsByTagName("video")[0

		);

		// dark background for improved video visibility

		tmp.contentWindow.document.body.style.backgroundColor="#222";

		}

};



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 and 

	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);

	}



	// Internet Archive library only, not Wayback Machine

	if ( is_archive_library() ) {

		// get media title from page title before first colon

		timerUI.newTitle = (document.location.href+"").substring((document.location.href+" ").search(/\/(?:.(?!\/))+\/?$/)+1 );

		// after last slash (additional space prevents full URL from being matched)

		timerUI.archive_org_title = document.title.substring(0,document.title.search(/:(?:.(?!:))+(?:.(?!:))+/)-1 );

		if (timerUI.archive_org_title.length > 0) /* only append " - " if a title exists. */ {

			/* only append " - " if a title exists. */

			if (timerUI.newTitle != "") { timerUI.newTitle += " - "; }

			timerUI.newTitle += timerUI.archive_org_title;

		}

		// trim after second-last colon, -1 to remove space at end



		timerUI.newTitle=decodeURI(timerUI.newTitle); // prevent spaces from turning into "%20".

	}

	if ( is_WaybackEmbed() ) {

		// Already generated by the browser inside the iframe. How convenient. Otherwise, a regular expression that matches the part after the last slash in the URL, and a decodeURI would have to be used.

		timerUI.newTitle = tmp.contentWindow.document.title;

	}

};



function is_archive_library() { 

	// function to check if the current page is an Archive.org library page and not the Wayback Machine

	return (isDomain(/^(www.)?archive.org/) && ! isDomain("web.archive.org") );

	// automatically returns true if the condition is met and false otherwise

}

function is_WaybackEmbed() {

	// checks if the current page is media embedded on the Wayback Machine, for code deduplication.

	if ( isDomain("web.archive.org") || isDomain("wayback.archive.org") && document.title=="Wayback Machine" && document.getElementsByTagName("iframe")[0 ) {

		// separate check for ID of iframe to avoid reference error

		if (document.getElementsByTagName("iframe")[0].id=="playback") {

			return true; 

		} 

	} else {

		return false;

	}

}





// == Master function ==

timeUI();
From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.

// == 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

var tmp="",count=0; // initiating temporary variables used inside functions and loops



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(extcount+"$", "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 media_symbol = {};

	media_symbol.play = "▶&#xFE0E;&thinsp;"; // thin space for alignment

	media_symbol.pause="&#10074;&thinsp;&#10074;"; // instead of "⏸" due to Edge browser putting an immutable blue box around it.

	media_symbol.stop="■";



// 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 "–&thinsp;–:–&thinsp;–:–&thinsp;–";



}



// 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 "–&thinsp;–:–&thinsp;–";



}





// 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", ".webm"],

	"audio":".mp3", ".wma", ".wav", ".ogg", ".opus", ".flac", ".oga", ".wma", ".aac", ".amr", ".alac", ".m4a"

};



// "replaceAll()" polyfill for pre-2020 browsers

	// based on: https://stackoverflow.com/a/1144788 

		

function escapeRegExp(string) {

	return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string

}



	// renamed function to prevent interference with "replaceAll()" on browsers since 2020

function replaceAll_polyfill(str, find, replacement) {

	return str.replace(new RegExp(escapeRegExp(find), 'g'), replacement);

}



// == 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.default_color = "rgb(49,136,255)"; // memorize default color

timerUI.gradient = "rgba(0,0,0,0.9)"; // using RGBA instead of hexadecimal for compatibility.

timerUI.font_pack = "din, futura, 'noto sans', 'open sans', ubuntu, 'segoe ui', verdana, tahoma, roboto, 'roboto light', arial, helvetica, 'trebuchet ms' ,'bitstream vera sans', 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 = "<span class=timer_linefeed><br /></span>";

var timer_slash = " <span class=timer_slash>/</span> ";



	// 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");

		}

		if (timerUI.on) { timerUI.on = false; return false; } else { timerUI.on = true; return false; }

	} 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");

		}

		if (timerUI.buffer_on) {

			timerUI.buffer_on = false; return false;

		} else {

			timerUI.buffer_on = true; return 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");

		}

		if (timerUI.title_on) {

			timerUI.title_on = false; return false;

		} else {

			timerUI.title_on = true; return true;

		}

	} else {

		console.warn(timerUI.msg.notimer);

		timeUI();

	}

};



timerUI.getTitle = function() { 

	if (! timerUI.domainRules_checked) /* only check domain rules once */ {

		timerUI.domainRules();

		timerUI.domainRules_checked = true;

	}

	if (customTitleElement) timerUI.newTitle = customTitleElement.innerHTML;

	else { // skipping this whole part if no custom title is specified

		timerUI.newTitle = document.title;

		// replace underscores with spaces

		timerUI.newTitle = replaceAll_polyfill(timerUI.newTitle, "_"," ");

		timerUI.titleDomainRules();

	}

	if (media_element) {

		timerUI.updateFileIcon();

		return timerUI.file_icon+"&nbsp;"+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 (! media_element.videoWidth) return "audio"; // Detects files that only contain audio, even if they have a video file extension.

    if (media_element.videoWidth > 0) return "video";

	if (checkFileExtension(mediafileext.video) ) return "video";

	if (checkFileExtension(mediafileext.audio) ) return "audio";

	return "unknown"; // if nothing detected

};



timerUI.updateFileIcon = function() {

	timerUI.file_icon = timerUI.guessMediaType();

	switch(timerUI.file_icon) {

		case "video": timerUI.file_icon = "🎞️"; break;

		case "audio": timerUI.file_icon = "♫"; break;

		case "unknown": timerUI.file_icon = "📄"; 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;

		}



		if (media_element.paused) {

			timerUI.status.innerHTML=media_symbol.pause;

		} else {

			timerUI.status.innerHTML=media_symbol.play;

		}

	} 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).start_pos,

			timerUI.get_buffer_range(count).end_pos

		);

	}

	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) {

	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");

	selectionsegment_number].style.left = start_pos / media_element.duration * 100 + "%";

	selectionsegment_number].style.width = (end_pos-start_pos) / media_element.duration * 100 + "%";

};







// colors

timerUI.setColor = function(newColor) {

	if (! timerUI.bar) {

		/* prevent running function before timerUI is properly loaded into the DOM to prevent exception */

		if (timerUI.debug_mode) console.debug("timerUI: setColor can not run before timerUI is properly loaded.");

		return false;

	}

	timerUI.previous_color = timerUI.color; // memorize previous setting

	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;



	// colour all buffer segments

	for (var count=0; count < timerUI.buffer_bar.childNodes.length; count++) {

		timerUI.buffer_bar.childNodescount].style.backgroundColor=timerUI.color;

	}

	if (timerUI.debug_mode) console.debug("timerUI: color changed from "+timerUI.previous_color+" to "+timerUI.color);

};



timerUI.setColor("default"); // prevent mixed colours if the code is run multiple times



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;

};





// light mode



timerUI.light_mode = false; // default



timerUI.light_mode_on = function() {

	timerUI.light_mode = true;

	timerUI.color_before_light_mode = timerUI.color;

	// improves visibility:

	if (timerUI.setColor) /* exists*/ {

		timerUI.setColor("lightblue");

	} else { return false; /* error */ }

	if (timerUI.gradient_placeholder) /* exists*/ {

		timerUI.gradient_placeholder.style.backgroundColor="rgba(0, 0, 0, 0.5)";

	} else { return false; /* error */ }

};



timerUI.light_mode_off = function() {

	timerUI.light_mode = false;

	if (timerUI.setColor) /* exists*/ {

		timerUI.setColor("lightblue");

	} else { return false; /* error */ }

	if (timerUI.gradient_placeholder) /* exists*/ {

		timerUI.gradient_placeholder.style.backgroundColor=""; // fall back to default

	} else { return false; /* error */ }

};



timerUI.toggle.light_mode = function() {

	if ( ! timerUI.light_mode ) /* if light mode is deactivated */ {

		timerUI.light_mode_on();

		return true;

	} else {

		timerUI.light_mode_off();

		return false;

	}

};



// "stop" icon



timerUI.stop = function() {

	timerUI.status.innerHTML=media_symbol.stop;

	timerUI.bar.style.width=0;

	timerUI.buffer_bar.style.width=0;

	// appearance of stopped timer consistent with the show_remaining setting

	if (timerUI.show_remaining == 2) {

		timerUI.time.innerHTML=MStimer(undefined)+" / "+MStimer(undefined);

	} else {

		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();

	// 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; padding:0; margin:0; }  #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%; left:0; bottom:0; pointer-events: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

		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%; left:0; 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%; left:0; bottom:0; opacity:0.4; } #timerUI .timerUI_buffer_segment { opacity:1; }", timerUI.div);





		// 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);







		// 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%; left:0; 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");



		// 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;", timerUI.div);



		timerUI.domainRules(); // load domain rules after initializing timer elements



	// 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) - "clickPos" has no "timerUI." notation because it is inside the 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 videos (Dailymotion only displays the video title in the HTML page title for public videos)

	}



	// activate light mode on wikis and Dailymotion due to bright backgrounds

	if ( isDomain("wiki") 

		|| isDomain("dailymotion.com") 

		|| isDomain("ghostarchive.org") 

		|| is_archive_library() ) {

		timerUI.light_mode_on();

	}



	// media embedded on Wayback Machine

	if ( is_WaybackEmbed() ) {

		tmp = document.getElementsByTagName("iframe")[0]; // iframe in temporary variable to deduplicate code

		customMediaElement(

			tmp.contentWindow.document.getElementsByTagName("video")[0

		);

		// dark background for improved video visibility

		tmp.contentWindow.document.body.style.backgroundColor="#222";

		}

};



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 and 

	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);

	}



	// Internet Archive library only, not Wayback Machine

	if ( is_archive_library() ) {

		// get media title from page title before first colon

		timerUI.newTitle = (document.location.href+"").substring((document.location.href+" ").search(/\/(?:.(?!\/))+\/?$/)+1 );

		// after last slash (additional space prevents full URL from being matched)

		timerUI.archive_org_title = document.title.substring(0,document.title.search(/:(?:.(?!:))+(?:.(?!:))+/)-1 );

		if (timerUI.archive_org_title.length > 0) /* only append " - " if a title exists. */ {

			/* only append " - " if a title exists. */

			if (timerUI.newTitle != "") { timerUI.newTitle += " - "; }

			timerUI.newTitle += timerUI.archive_org_title;

		}

		// trim after second-last colon, -1 to remove space at end



		timerUI.newTitle=decodeURI(timerUI.newTitle); // prevent spaces from turning into "%20".

	}

	if ( is_WaybackEmbed() ) {

		// Already generated by the browser inside the iframe. How convenient. Otherwise, a regular expression that matches the part after the last slash in the URL, and a decodeURI would have to be used.

		timerUI.newTitle = tmp.contentWindow.document.title;

	}

};



function is_archive_library() { 

	// function to check if the current page is an Archive.org library page and not the Wayback Machine

	return (isDomain(/^(www.)?archive.org/) && ! isDomain("web.archive.org") );

	// automatically returns true if the condition is met and false otherwise

}

function is_WaybackEmbed() {

	// checks if the current page is media embedded on the Wayback Machine, for code deduplication.

	if ( isDomain("web.archive.org") || isDomain("wayback.archive.org") && document.title=="Wayback Machine" && document.getElementsByTagName("iframe")[0 ) {

		// separate check for ID of iframe to avoid reference error

		if (document.getElementsByTagName("iframe")[0].id=="playback") {

			return true; 

		} 

	} else {

		return false;

	}

}





// == Master function ==

timeUI();

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook