Web media timer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// == 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(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 media_symbol = {}; | |
media_symbol.play = "▶︎ "; // thin space for alignment | |
media_symbol.pause="❚ ❚"; // 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 "– –:– –:– –"; | |
} | |
// 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", ".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+" "+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"); | |
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) { | |
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.childNodes[count].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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment