- video will now unpause when clicking 'SYNC ON' button
- sync offset buttons now change offset by 0.2s (was 0.1s)
- added extension popup with list of latest F1TV videos
- added extension files
This commit is contained in:
najdek committed 2024-02-16 21:13:28 +01:00
1 parent fa99222f9b
commit 9d1ab602c6
11 files changed
+703 -452

No files matched your search

@@ -1,452 +1,455 @@
// ==UserScript==
// @name Carbon for F1TV
// @namespace https://Carbon-for-F1TV.github.io/Carbon-for-F1TV/
// @match https://f1tv.formula1.com/*
// @version 1.0.0
// @author Carbon-for-F1TV
// @description Enhance your F1TV experience
// @require https://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==
var DEFAULT_THEATERMODE = true;
var theatermode_btn_image = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIGlkPSdMYXllcl8xJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4PScwcHgnIHk9JzBweCcgdmlld0JveD0nMCAwIDI4My41IDQyNS4yJyBzdHlsZT0nZW5hYmxlLWJhY2tncm91bm" +
"Q6bmV3IDAgMCAyODMuNSA0MjUuMjsnIHhtbDpzcGFjZT0ncHJlc2VydmUnPjxnPjxwYXRoIHN0eWxlPSdmaWxsOiNEMEQwRDI7JyBkPSdNMjM5LjMsMTI0LjlINDQuMmMtMTAuNywwLTE5LjQsOC43LTE5LjQsMTkuNHYxMzYuNmMwLDEwLjcsOC43LDE5LjQsMTkuNCwxOS40aDE5NS4xICBjMTAuNywwLDE5LjQtOC43LDE5LjQtMTkuNFYxNDQuM0MyNTguNywxMzMuNiwyNTAsMT" +
"I0LjksMjM5LjMsMTI0Ljl6IE04Ni43LDI4Ny40SDM4di00OC43aDE5LjV2MjkuMmgyOS4yVjI4Ny40eiBNODYuNywxNTYuMiBINTcuNXYyOS4ySDM4di00OC43aDQ4LjdWMTU2LjJ6IE0yNDUuNSwyODcuNGgtNDguN3YtMTkuNUgyMjZ2LTI5LjJoMTkuNVYyODcuNHogTTI0NS41LDE4NS40SDIyNnYtMjkuMmgtMjkuMnYtMTkuNWg0OC43VjE4NS40eicvPjwvZz48L3N2Zz4NCg==";
var syncoffset_btn_image = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDI4My41IDQyNS4yIiBzdHlsZT0iZW5hYmxlLWJhY2tncm9" +
"1bmQ6bmV3IDAgMCAyODMuNSA0MjUuMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGcgc3R5bGU9ImRpc3BsYXk6bm9uZTsiPg0KCTxwYXRoIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNEMEQwRDI7IiBkPSJNMjM5LjMsMTI0LjlINDQuMmMtMTAuNywwLTE5LjQsOC43LTE5LjQsMTkuNHYxMzYuNmMwLDEwLjcsOC43LDE5LjQsMTkuNCwxOS40DQoJCWgxOTUuMWMxMC4" +
"3LDAsMTkuNC04LjcsMTkuNC0xOS40VjE0NC4zQzI1OC43LDEzMy42LDI1MCwxMjQuOSwyMzkuMywxMjQuOXogTTg2LjcsMjg3LjRIMzh2LTQ4LjdoMTkuNXYyOS4yaDI5LjJWMjg3LjR6DQoJCSBNODYuNywxNTYuMkg1Ny41djI5LjJIMzh2LTQ4LjdoNDguN1YxNTYuMnogTTI0NS41LDI4Ny40aC00OC43di0xOS41SDIyNnYtMjkuMmgxOS41VjI4Ny40eiBNMjQ1LjUsMTg1LjR" +
"IMjI2di0yOS4yaC0yOS4ydi0xOS41DQoJCWg0OC43VjE4NS40eiIvPg0KPC9nPg0KPHBhdGggc3R5bGU9ImZpbGw6I0QwRDBEMjsiIGQ9Ik0yOS43LDIwMy41bDEwOS43LTc3LjhjMi45LTIsNy4yLTAuMiw3LjIsM3YxNjcuNmMwLDMuMy00LjMsNS4xLTcuMiwzTDI5LjcsMjIxLjcNCglDMjMuMiwyMTcsMjMuMiwyMDguMiwyOS43LDIwMy41eiIvPg0KPHBhdGggc3R5bGU9ImZ" +
"pbGw6I0QwRDBEMjsiIGQ9Ik0xNDEuOCwyMDMuNWwxMDkuNy03Ny44YzIuOS0yLDcuMi0wLjIsNy4yLDN2MTY3LjZjMCwzLjMtNC4zLDUuMS03LjIsM2wtMTA5LjctNzcuOA0KCUMxMzUuMiwyMTcsMTM1LjIsMjA4LjIsMTQxLjgsMjAzLjV6Ii8+DQo8L3N2Zz4=";
var theatermode_active = DEFAULT_THEATERMODE;
var carbon_mode = "default";
if (window.location.hash.split("_")[0] == "#carbon") {
carbon_mode = window.location.hash.split("_")[1];
}
function log(msg) {
let d = new Date();
let dt = d.toISOString().split('T')[0] + " " + d.toTimeString().split(' ')[0];
console.log("[Carbon for F1TV] [" + dt + "]: " + msg);
}
function isTheaterMode() {
if ($("#carbon-helper .carbon-theatermode-style").length > 0) {
return true;
} else {
return false;
}
}
function toggleTheaterMode(temporary) {
log("Theater mode toggle");
if (isTheaterMode()) {
//disabling theater mode
$("#carbon-helper .carbon-theatermode-style").remove();
if (temporary !== true) {
theatermode_active = false;
}
} else {
let theaterModeStyleHtml = "<div class='carbon-theatermode-style'>" +
"<style>" +
"body {overflow: hidden;}" +
".inset-video-item-image-container {position: fixed !important; z-index: 1000; top: 0; left: 0; height: 100%; width: 100%; margin: 0 !important; background-color: #000;}" +
".inset-video-item-image {margin-top: 50vh; transform: translateY(-50%);}" +
".inset-video-item-play-action-container {width: 100%;}" +
"</style>" +
"</div>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", theaterModeStyleHtml);
theatermode_active = true;
}
}
var popouts = [];
function addPopout() {
let popoutId;
let createTime = Date.now();
if ($("#carbon-helper .carbon-popout").length > 0) {
popoutId = parseInt($("#carbon-helper .carbon-popout").last()[0].id.split("carbon-popout-")[1]) + 1;
} else {
popoutId = 0;
}
log("creating popout [id: " + popoutId + "]");
let popoutHtml = "<div class='carbon-popout' id='carbon-popout-" + popoutId + "' style='display: none;'>" +
"<div id='carbon-popout-" + popoutId + "-create-time'>" + createTime + "</div>" +
"</div>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", popoutHtml);
let popoutUrl = document.location.href.split("#")[0];
if (popoutUrl.includes("?action=play")) {
popoutUrl += "#carbon_popout";
} else {
popoutUrl += "?action=play#carbon_popout";
}
popouts["popout-" + popoutId] = window.open(popoutUrl, createTime, "width=1280,height=720");
console.log(popouts["popout-" + popoutId]);
}
function deletePopout(p) {
let id = parseInt(p.split("-")[1]);
let createTime = $("#carbon-popout-" + id + "-create-time").text();
let currentTime = Date.now();
let timeDiff = Math.round((currentTime - createTime)/1000);
let deleteAfterSeconds = 20;
if (timeDiff > deleteAfterSeconds) {
$("#carbon-popout-" + id).remove();
delete popouts[p];
log("popout closed [id: " + id + "]");
} else {
log("popout [id: " + id + "] not responding [" + timeDiff + "/" + deleteAfterSeconds + "s]");
}
}
if (carbon_mode !== "popout") {
var pushProgressToPopouts = setInterval(function() {
// delete closed popouts
for (let p in popouts) {
try {
if (popouts[p].document.location) {
} else {
deletePopout(p);
}
} catch (e) {
deletePopout(p);
}
}
for (let p in popouts) {
try {
popouts[p].document.getElementById("main-progress").textContent = $(".carbon-player video")[0].currentTime;
popouts[p].document.getElementById("progress-sync-time").textContent = Date.now();
let mainState;
if ($(".carbon-player video")[0].paused) {
mainState = "paused";
} else {
mainState = "playing";
}
popouts[p].document.getElementById("main-state").textContent = mainState;
popouts[p].document.getElementById("main-playbackrate").textContent = $(".carbon-player video")[0].playbackRate;
} catch (e) {
let id = parseInt(p.split("-")[1]);
log("popout not loaded [id: " + id + "]");
}
}
}, 2000);
}
if (carbon_mode == "popout") {
var syncData = setInterval(function() {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
let thisProgressRealtime = $(".carbon-player video")[0].currentTime;
$("#this-progress-realtime").text(thisProgressRealtime);
let estimatedMainProgress;
if ($("#main-state").text() == "paused") {
$("#main-progress-realtime").text($("#main-progress").text());
} else {
let timeDifference = (Date.now() - parseInt($("#progress-sync-time").text()))/1000;
let mainPlaybackrate = parseFloat($("#main-playbackrate").text());
estimatedMainProgress = parseInt($("#main-progress").text()) + (timeDifference * mainPlaybackrate);
$("#main-progress-realtime").text(estimatedMainProgress);
}
let thisPlaybackrate = $(".carbon-player video")[0].playbackRate;
$("#this-playbackrate").text(thisPlaybackrate);
let targetProgress = estimatedMainProgress;
let syncOffset = parseInt(document.getElementById("sync-offset").value) / 1000 || 0;
targetProgress += syncOffset;
$("#this-progress-target").text(targetProgress);
let diffToTarget = targetProgress - thisProgressRealtime;
$("#diff-to-target").text(diffToTarget);
} else {
log("can't sync, player not loaded");
}
}, 500);
let syncNow = setInterval(function() {
let syncMode = parseInt(document.getElementById("sync-mode").value);
if (syncMode == 1) {
let thisProgressRealtime = parseFloat($("#this-progress-realtime").text());
let targetProgress = parseFloat($("#this-progress-target").text());
let mainPlaybackrate = parseFloat($("#main-playbackrate").text());
let thisPlaybackrate = parseFloat($("#this-playbackrate").text());
let diffToTarget = targetProgress - thisProgressRealtime;
let diffToTargetAbs = Math.abs(diffToTarget);
if (diffToTargetAbs > 15) {
videoJumpToProgress(targetProgress);
} else if (diffToTargetAbs > 10) {
if (diffToTarget > 0) { videoSpeed(3); } else { videoSpeed(0.333); }
} else if (diffToTargetAbs > 5) {
if (diffToTarget > 0) { videoSpeed(2); } else { videoSpeed(0.5); }
} else if (diffToTargetAbs > 1) {
if (diffToTarget > 0) { videoSpeed(1.5); } else { videoSpeed(0.666); }
} else if (diffToTargetAbs > 0.5) {
if (diffToTarget > 0) { videoSpeed(1.25); } else { videoSpeed(0.8); }
} else if (diffToTargetAbs > 0.2) {
if (diffToTarget > 0) { videoSpeed(1.11); } else { videoSpeed(0.9); }
} else {
videoSpeed(1);
}
}
}, 500);
}
function videoSpeed(speed) {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
$(".carbon-player video")[0].playbackRate = speed;
} else {
log("can't set speed, player not loaded");
}
}
function videoJumpToProgress(progress) {
$(".carbon-player video")[0].currentTime = progress + 10;
// DIRTY FIX:
// Simulates click on seek-backward button.
// This fixes Bitmovin player sometimes not buffering video after setting it's currentTime value.
$(".carbon-player .bmpui-ui-rewindbutton")[0].click();
}
function injectPlayerFeatures() {
log("Injecting player features");
$(".player-container.shown .bitmovinplayer-container").addClass("carbon-player");
var multi_channels;
if ($(".embedded-player-container .channel-switcher-container").length > 0) {
log("player has multiple channels");
multi_channels = true;
} else {
multi_channels = false;
}
// show player speed toggle
$(".carbon-player .bmpui-ui-playbackspeedselectbox").parent().parent().removeClass("bmpui-hidden");
// PIP BUTTON
/*
if ((window.navigator.userAgent.indexOf("Firefox") == -1) // not on Firefox
&& (carbon_mode !== "popout")) { // not in popout mode
// show pip button
$(".carbon-player .bmpui-ui-piptogglebutton").removeClass("bmpui-hidden");
// fix pip button
var pipBtn = $(".carbon-player .bmpui-ui-piptogglebutton")[0];
$(pipBtn).addClass("carbon-btn-piptoggle");
pipBtn.replaceWith(pipBtn.cloneNode(true));
$(".carbon-player .carbon-btn-piptoggle").on("click", function() {
log("Requesting PictureInPicture");
$(".carbon-player video")[0].requestPictureInPicture();
})
}
*/
// add theater mode button
if (carbon_mode !== "popout") {
let theaterBtnHtml = "<button aria-label='Theater mode' class='carbon-btn-theatermode bmpui-off' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + theatermode_btn_image + ")' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Theater mode</span></button>";
$(".carbon-player .bmpui-container-wrapper .bmpui-ui-piptogglebutton")[0].insertAdjacentHTML("beforebegin", theaterBtnHtml);
$(".carbon-player .carbon-btn-theatermode").on("click", function() {
toggleTheaterMode();
})
}
// set theater mode state
if ((!isTheaterMode() && (theatermode_active == true)) || (!isTheaterMode() && (carbon_mode == "popout"))) {
toggleTheaterMode();
}
if ((multi_channels == true) && (carbon_mode !== "popout")) {
let popoutBtnHtml = "<button aria-label='New Popout' class='carbon-btn-popout bmpui-ui-piptogglebutton bmpui-off' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label'>New Popout</span></button>";
$(".carbon-player .bmpui-container-wrapper .carbon-btn-theatermode")[0].insertAdjacentHTML("beforebegin", popoutBtnHtml);
$(".carbon-player .carbon-btn-popout").on("click", function() {
addPopout();
})
}
if (carbon_mode == "popout") {
$(".carbon-player .bmpui-ui-casttogglebutton").addClass("bmpui-hidden");
//$(".carbon-player .bmpui-ui-forwardbutton").addClass("bmpui-hidden");
//$(".carbon-player .bmpui-ui-rewindbutton").addClass("bmpui-hidden");
let syncToggleHtml = "<span class='carbon-btn-synctoggle bmpui-ui-playbacktimelabel' style='cursor: pointer; min-width: 80px; position: relative; padding-left: 18px;'><span class='carbon-btn-synctoggle-label'>SYNC ON</span><div class='carbon-btn-synctoggle-dot' style='background-color: #56ff63; width: 8px; height: 8px; border-radius: 8px; position: absolute; left: 0; top: 5px;'></div></span>";
$(".carbon-player .bmpui-controlbar-top .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", syncToggleHtml);
$(".carbon-btn-synctoggle")[0].addEventListener("click", function() {
toggleSyncMode();
});
$(".carbon-player .bmpui-seekbar")[0].addEventListener("click", function() {
toggleSyncMode(0);
});
$(".carbon-player .bmpui-ui-rewindbutton")[0].addEventListener("mouseup", function() { //can't be listening to "click" event on rewind button, as we're simulating it elsewhere to fix sync mode
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-forwardbutton")[0].addEventListener("mouseup", function() {
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-playbacktogglebutton")[0].addEventListener("mouseup", function() {
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-hugeplaybacktogglebutton")[0].addEventListener("mouseup", function() {
toggleSyncMode(0);
})
let syncDebugToggleHtml = "<div class='carbon-sync-debug-toggle bmpui-ui-settings-panel-item' style='cursor: pointer;' role='menuitem'><div class='bmpui-container-wrapper' style='cursor: pointer;'><label class='bmpui-ui-label' style='cursor: pointer;'>SYNC MODE DEBUG</label></div></div>";
$(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", syncDebugToggleHtml);
$(".carbon-sync-debug-toggle")[0].addEventListener("click", function() {
toggleSyncDebug();
})
let syncOffsetSwitcherHtml = "<span class='bmpui-ui-playbacktimelabel carbon-syncoffset-menu' style='font-size: 13px; line-height: 28px; padding-left: 14px; padding-right: 4px;'>Sync Offset:</span>" +
"<button class='carbon-btn-syncoffset-back bmpui-off carbon-syncoffset-menu' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + syncoffset_btn_image + ");' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Sync Offset (-)</span></button>" +
"<span class='bmpui-ui-playbacktimelabel carbon-syncoffset-view carbon-syncoffset-menu' style='font-size: 18px; line-height: 28px; padding: 0px 10px; min-width: 52px; text-align: center;'></span>" +
"<button class='carbon-btn-syncoffset-forward bmpui-off carbon-syncoffset-menu' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + syncoffset_btn_image + "); transform: rotate(180deg);' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Sync Offset (+)</span></button>";
$(".carbon-player .bmpui-container-wrapper .bmpui-ui-volumeslider")[0].insertAdjacentHTML("afterEnd", syncOffsetSwitcherHtml);
updateSyncOffsetView();
$(".carbon-player .carbon-btn-syncoffset-back").on("click", function() {
setSyncOffset(-100);
})
$(".carbon-player .carbon-btn-syncoffset-forward").on("click", function() {
setSyncOffset(+100);
})
}
let donateHtml = "<div class='carbon-sync-debug-toggle bmpui-ui-settings-panel-item' role='menuitem'><a style='color: #ff6643; text-align: center; display: block;' href='https://github.com/Carbon-for-F1TV/Carbon-for-F1TV/blob/master/DONATE.md' target='_blank'>❤ Donate to support Carbon for F1TV</a></div>";
$(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("beforeEnd", donateHtml);
}
function updateSyncOffsetView() {
let syncOffset = $("#sync-offset")[0].value || 0;
syncOffset = (Math.round(syncOffset) / 1000).toFixed(1);
$(".carbon-player .carbon-syncoffset-view").text(syncOffset);
}
function setSyncOffset(diff) {
let syncOffset = $("#sync-offset")[0].value || 0;
let newSyncOffset = parseInt(syncOffset) + diff;
$("#sync-offset")[0].value = newSyncOffset;
updateSyncOffsetView();
}
function toggleSyncDebug() {
if ($("#carbon-helper .sync-data").is(":visible")) {
$("#carbon-helper .sync-data").hide();
} else {
$("#carbon-helper .sync-data").show();
}
}
function toggleSyncMode(mode) {
let syncMode = parseInt(document.getElementById("sync-mode").value);
if (syncMode == 1 || mode == 0) {
log("sync mode disabled");
document.getElementById("sync-mode").value = 0;
$(".carbon-btn-synctoggle-label").text("SYNC OFF");
$(".carbon-btn-synctoggle-dot").css("background-color", "#999");
$(".carbon-syncoffset-menu").hide();
videoSpeed(1);
} else {
log("sync mode enabled");
document.getElementById("sync-mode").value = 1;
$(".carbon-btn-synctoggle-label").text("SYNC ON");
$(".carbon-btn-synctoggle-dot").css("background-color", "#56ff63");
$(".carbon-syncoffset-menu").show();
}
}
function waitForPlayer() {
if ($(".player-container.shown .bitmovinplayer-container").length > 0) {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
} else {
if ($(".player-container.shown .bitmovinplayer-container video")[0].readyState > 0) {
log("new player loaded");
injectPlayerFeatures();
// slow down loop
clearInterval(playerInjectLoop);
playerInjectLoop = setInterval(waitForPlayer, 1000);
}
}
} else {
//no player found
}
}
function waitForPageLoad() {
if ($("#app .app-wrapper").length > 0) {
clearInterval(waitForPageLoad);
$("#app")[0].insertAdjacentHTML("beforeEnd", "<div id='carbon-helper'></div>");
if (carbon_mode == "popout") {
let syncDataHtml = "<div class='sync-data'>" +
"<div>main window progress: <span id='main-progress'></span>, estimated realtime: <span class='thiswindow' id='main-progress-realtime'></span></div>" +
"<div>this window progress: <span class='thiswindow' id='this-progress-realtime'></span></div>" +
"<div>sync offset: <input id='sync-offset' type='number' step='250' value='' style='width: 80px;'>, target progress: <span class='thiswindow' id='this-progress-target'></span></div>" +
"<div>progress sync time: <span id='progress-sync-time'></span></div>" +
"<div>diff to target: <span class='thiswindow' id='diff-to-target'></span></div><br>" +
"<div>main window state: <span id='main-state'></span></div>" +
"<div>sync mode: <input id='sync-mode' type='number' step='1' value='1' style='width: 40px;'></div>" +
"<div>main window playback rate: <span id='main-playbackrate'></span></div>" +
"<div>this window playback rate: <span class='thiswindow' id='this-playbackrate'></span></div>" +
"</div>" +
"<style>" +
".sync-data {position: fixed; top: 0; left: 0; background-color: #000000bb; color: #aaa; padding: 6px; z-index: 1001; font-family: monospace; display: none;}" +
".sync-data span {font-weight: bold; color: #fff;}" +
".sync-data span.thiswindow {font-weight: bold; color: #ffff00;}" +
"</style>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", syncDataHtml);
}
}
}
var waitForPageLoad = setInterval(waitForPageLoad, 50);
var playerInjectLoop;
function pageChanged() {
clearInterval(playerInjectLoop);
if (document.location.href.split("formula1.com/")[1].split("/")[0] == "detail") {
log("Page changed (video)");
playerInjectLoop = setInterval(waitForPlayer, 50);
} else {
log("Page changed (not video)");
if (isTheaterMode()) {
toggleTheaterMode(true);
}
}
}
let oldHref = document.location.href;
let observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (oldHref != document.location.href) {
oldHref = document.location.href;
pageChanged();
}
});
});
observer.observe(document.querySelector("body"), {
childList: true,
subtree: true
});
pageChanged();
// ==UserScript==
// @name Carbon for F1TV
// @namespace https://Carbon-for-F1TV.github.io/Carbon-for-F1TV/
// @match https://f1tv.formula1.com/*
// @version 1.0.1
// @author Carbon-for-F1TV
// @description Enhance your F1TV experience
// @require https://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==
var DEFAULT_THEATERMODE = true;
var theatermode_btn_image = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIGlkPSdMYXllcl8xJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4PScwcHgnIHk9JzBweCcgdmlld0JveD0nMCAwIDI4My41IDQyNS4yJyBzdHlsZT0nZW5hYmxlLWJhY2tncm91bm" +
"Q6bmV3IDAgMCAyODMuNSA0MjUuMjsnIHhtbDpzcGFjZT0ncHJlc2VydmUnPjxnPjxwYXRoIHN0eWxlPSdmaWxsOiNEMEQwRDI7JyBkPSdNMjM5LjMsMTI0LjlINDQuMmMtMTAuNywwLTE5LjQsOC43LTE5LjQsMTkuNHYxMzYuNmMwLDEwLjcsOC43LDE5LjQsMTkuNCwxOS40aDE5NS4xICBjMTAuNywwLDE5LjQtOC43LDE5LjQtMTkuNFYxNDQuM0MyNTguNywxMzMuNiwyNTAsMT" +
"I0LjksMjM5LjMsMTI0Ljl6IE04Ni43LDI4Ny40SDM4di00OC43aDE5LjV2MjkuMmgyOS4yVjI4Ny40eiBNODYuNywxNTYuMiBINTcuNXYyOS4ySDM4di00OC43aDQ4LjdWMTU2LjJ6IE0yNDUuNSwyODcuNGgtNDguN3YtMTkuNUgyMjZ2LTI5LjJoMTkuNVYyODcuNHogTTI0NS41LDE4NS40SDIyNnYtMjkuMmgtMjkuMnYtMTkuNWg0OC43VjE4NS40eicvPjwvZz48L3N2Zz4NCg==";
var syncoffset_btn_image = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDI4My41IDQyNS4yIiBzdHlsZT0iZW5hYmxlLWJhY2tncm9" +
"1bmQ6bmV3IDAgMCAyODMuNSA0MjUuMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGcgc3R5bGU9ImRpc3BsYXk6bm9uZTsiPg0KCTxwYXRoIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNEMEQwRDI7IiBkPSJNMjM5LjMsMTI0LjlINDQuMmMtMTAuNywwLTE5LjQsOC43LTE5LjQsMTkuNHYxMzYuNmMwLDEwLjcsOC43LDE5LjQsMTkuNCwxOS40DQoJCWgxOTUuMWMxMC4" +
"3LDAsMTkuNC04LjcsMTkuNC0xOS40VjE0NC4zQzI1OC43LDEzMy42LDI1MCwxMjQuOSwyMzkuMywxMjQuOXogTTg2LjcsMjg3LjRIMzh2LTQ4LjdoMTkuNXYyOS4yaDI5LjJWMjg3LjR6DQoJCSBNODYuNywxNTYuMkg1Ny41djI5LjJIMzh2LTQ4LjdoNDguN1YxNTYuMnogTTI0NS41LDI4Ny40aC00OC43di0xOS41SDIyNnYtMjkuMmgxOS41VjI4Ny40eiBNMjQ1LjUsMTg1LjR" +
"IMjI2di0yOS4yaC0yOS4ydi0xOS41DQoJCWg0OC43VjE4NS40eiIvPg0KPC9nPg0KPHBhdGggc3R5bGU9ImZpbGw6I0QwRDBEMjsiIGQ9Ik0yOS43LDIwMy41bDEwOS43LTc3LjhjMi45LTIsNy4yLTAuMiw3LjIsM3YxNjcuNmMwLDMuMy00LjMsNS4xLTcuMiwzTDI5LjcsMjIxLjcNCglDMjMuMiwyMTcsMjMuMiwyMDguMiwyOS43LDIwMy41eiIvPg0KPHBhdGggc3R5bGU9ImZ" +
"pbGw6I0QwRDBEMjsiIGQ9Ik0xNDEuOCwyMDMuNWwxMDkuNy03Ny44YzIuOS0yLDcuMi0wLjIsNy4yLDN2MTY3LjZjMCwzLjMtNC4zLDUuMS03LjIsM2wtMTA5LjctNzcuOA0KCUMxMzUuMiwyMTcsMTM1LjIsMjA4LjIsMTQxLjgsMjAzLjV6Ii8+DQo8L3N2Zz4=";
var theatermode_active = DEFAULT_THEATERMODE;
var carbon_mode = "default";
if (window.location.hash.split("_")[0] == "#carbon") {
carbon_mode = window.location.hash.split("_")[1];
}
function log(msg) {
let d = new Date();
let dt = d.toISOString().split('T')[0] + " " + d.toTimeString().split(' ')[0];
console.log("[Carbon for F1TV] [" + dt + "]: " + msg);
}
function isTheaterMode() {
if ($("#carbon-helper .carbon-theatermode-style").length > 0) {
return true;
} else {
return false;
}
}
function toggleTheaterMode(temporary) {
log("Theater mode toggle");
if (isTheaterMode()) {
//disabling theater mode
$("#carbon-helper .carbon-theatermode-style").remove();
if (temporary !== true) {
theatermode_active = false;
}
} else {
let theaterModeStyleHtml = "<div class='carbon-theatermode-style'>" +
"<style>" +
"body {overflow: hidden;}" +
".inset-video-item-image-container {position: fixed !important; z-index: 1000; top: 0; left: 0; height: 100%; width: 100%; margin: 0 !important; background-color: #000;}" +
".inset-video-item-image {margin-top: 50vh; transform: translateY(-50%);}" +
".inset-video-item-play-action-container {width: 100%;}" +
"</style>" +
"</div>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", theaterModeStyleHtml);
theatermode_active = true;
}
}
var popouts = [];
function addPopout() {
let popoutId;
let createTime = Date.now();
if ($("#carbon-helper .carbon-popout").length > 0) {
popoutId = parseInt($("#carbon-helper .carbon-popout").last()[0].id.split("carbon-popout-")[1]) + 1;
} else {
popoutId = 0;
}
log("creating popout [id: " + popoutId + "]");
let popoutHtml = "<div class='carbon-popout' id='carbon-popout-" + popoutId + "' style='display: none;'>" +
"<div id='carbon-popout-" + popoutId + "-create-time'>" + createTime + "</div>" +
"</div>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", popoutHtml);
let popoutUrl = document.location.href.split("#")[0];
if (popoutUrl.includes("?action=play")) {
popoutUrl += "#carbon_popout";
} else {
popoutUrl += "?action=play#carbon_popout";
}
popouts["popout-" + popoutId] = window.open(popoutUrl, createTime, "width=1280,height=720");
console.log(popouts["popout-" + popoutId]);
}
function deletePopout(p) {
let id = parseInt(p.split("-")[1]);
let createTime = $("#carbon-popout-" + id + "-create-time").text();
let currentTime = Date.now();
let timeDiff = Math.round((currentTime - createTime) / 1000);
let deleteAfterSeconds = 20;
if (timeDiff > deleteAfterSeconds) {
$("#carbon-popout-" + id).remove();
delete popouts[p];
log("popout closed [id: " + id + "]");
} else {
log("popout [id: " + id + "] not responding [" + timeDiff + "/" + deleteAfterSeconds + "s]");
}
}
if (carbon_mode !== "popout") {
var pushProgressToPopouts = setInterval(function () {
// delete closed popouts
for (let p in popouts) {
try {
if (popouts[p].document.location) {
} else {
deletePopout(p);
}
} catch (e) {
deletePopout(p);
}
}
for (let p in popouts) {
try {
popouts[p].document.getElementById("main-progress").textContent = $(".carbon-player video")[0].currentTime;
popouts[p].document.getElementById("progress-sync-time").textContent = Date.now();
let mainState;
if ($(".carbon-player video")[0].paused) {
mainState = "paused";
} else {
mainState = "playing";
}
popouts[p].document.getElementById("main-state").textContent = mainState;
popouts[p].document.getElementById("main-playbackrate").textContent = $(".carbon-player video")[0].playbackRate;
} catch (e) {
let id = parseInt(p.split("-")[1]);
log("popout not loaded [id: " + id + "]");
}
}
}, 2000);
}
if (carbon_mode == "popout") {
var syncData = setInterval(function () {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
let thisProgressRealtime = $(".carbon-player video")[0].currentTime;
$("#this-progress-realtime").text(thisProgressRealtime);
let estimatedMainProgress;
if ($("#main-state").text() == "paused") {
$("#main-progress-realtime").text($("#main-progress").text());
} else {
let timeDifference = (Date.now() - parseInt($("#progress-sync-time").text())) / 1000;
let mainPlaybackrate = parseFloat($("#main-playbackrate").text());
estimatedMainProgress = parseInt($("#main-progress").text()) + (timeDifference * mainPlaybackrate);
$("#main-progress-realtime").text(estimatedMainProgress);
}
let thisPlaybackrate = $(".carbon-player video")[0].playbackRate;
$("#this-playbackrate").text(thisPlaybackrate);
let targetProgress = estimatedMainProgress;
let syncOffset = parseInt(document.getElementById("sync-offset").value) / 1000 || 0;
targetProgress += syncOffset;
$("#this-progress-target").text(targetProgress);
let diffToTarget = targetProgress - thisProgressRealtime;
$("#diff-to-target").text(diffToTarget);
} else {
log("can't sync, player not loaded");
}
}, 500);
let syncNow = setInterval(function () {
let syncMode = parseInt(document.getElementById("sync-mode").value);
if (syncMode == 1) {
let thisProgressRealtime = parseFloat($("#this-progress-realtime").text());
let targetProgress = parseFloat($("#this-progress-target").text());
let mainPlaybackrate = parseFloat($("#main-playbackrate").text());
let thisPlaybackrate = parseFloat($("#this-playbackrate").text());
let diffToTarget = targetProgress - thisProgressRealtime;
let diffToTargetAbs = Math.abs(diffToTarget);
if (diffToTargetAbs > 15) {
videoJumpToProgress(targetProgress);
} else if (diffToTargetAbs > 10) {
if (diffToTarget > 0) { videoSpeed(3); } else { videoSpeed(0.333); }
} else if (diffToTargetAbs > 5) {
if (diffToTarget > 0) { videoSpeed(2); } else { videoSpeed(0.5); }
} else if (diffToTargetAbs > 1) {
if (diffToTarget > 0) { videoSpeed(1.5); } else { videoSpeed(0.666); }
} else if (diffToTargetAbs > 0.5) {
if (diffToTarget > 0) { videoSpeed(1.25); } else { videoSpeed(0.8); }
} else if (diffToTargetAbs > 0.2) {
if (diffToTarget > 0) { videoSpeed(1.11); } else { videoSpeed(0.9); }
} else {
videoSpeed(1);
}
}
}, 500);
}
function videoSpeed(speed) {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
$(".carbon-player video")[0].playbackRate = speed;
} else {
log("can't set speed, player not loaded");
}
}
function videoJumpToProgress(progress) {
$(".carbon-player video")[0].currentTime = progress + 10;
// DIRTY FIX:
// Simulates click on seek-backward button.
// This fixes Bitmovin player sometimes not buffering video after setting it's currentTime value.
$(".carbon-player .bmpui-ui-rewindbutton")[0].click();
}
function injectPlayerFeatures() {
log("Injecting player features");
$(".player-container.shown .bitmovinplayer-container").addClass("carbon-player");
var multi_channels;
if ($(".embedded-player-container .channel-switcher-container").length > 0) {
log("player has multiple channels");
multi_channels = true;
} else {
multi_channels = false;
}
// show player speed toggle
$(".carbon-player .bmpui-ui-playbackspeedselectbox").parent().parent().removeClass("bmpui-hidden");
// PIP BUTTON
/*
if ((window.navigator.userAgent.indexOf("Firefox") == -1) // not on Firefox
&& (carbon_mode !== "popout")) { // not in popout mode
// show pip button
$(".carbon-player .bmpui-ui-piptogglebutton").removeClass("bmpui-hidden");
// fix pip button
var pipBtn = $(".carbon-player .bmpui-ui-piptogglebutton")[0];
$(pipBtn).addClass("carbon-btn-piptoggle");
pipBtn.replaceWith(pipBtn.cloneNode(true));
$(".carbon-player .carbon-btn-piptoggle").on("click", function() {
log("Requesting PictureInPicture");
$(".carbon-player video")[0].requestPictureInPicture();
})
}
*/
// add theater mode button
if (carbon_mode !== "popout") {
let theaterBtnHtml = "<button aria-label='Theater mode' class='carbon-btn-theatermode bmpui-off' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + theatermode_btn_image + ")' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Theater mode</span></button>";
$(".carbon-player .bmpui-container-wrapper .bmpui-ui-piptogglebutton")[0].insertAdjacentHTML("beforebegin", theaterBtnHtml);
$(".carbon-player .carbon-btn-theatermode").on("click", function () {
toggleTheaterMode();
})
}
// set theater mode state
if ((!isTheaterMode() && (theatermode_active == true)) || (!isTheaterMode() && (carbon_mode == "popout"))) {
toggleTheaterMode();
}
if ((multi_channels == true) && (carbon_mode !== "popout")) {
let popoutBtnHtml = "<button aria-label='New Popout' class='carbon-btn-popout bmpui-ui-piptogglebutton bmpui-off' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label'>New Popout</span></button>";
$(".carbon-player .bmpui-container-wrapper .carbon-btn-theatermode")[0].insertAdjacentHTML("beforebegin", popoutBtnHtml);
$(".carbon-player .carbon-btn-popout").on("click", function () {
addPopout();
})
}
if (carbon_mode == "popout") {
$(".carbon-player .bmpui-ui-casttogglebutton").addClass("bmpui-hidden");
//$(".carbon-player .bmpui-ui-forwardbutton").addClass("bmpui-hidden");
//$(".carbon-player .bmpui-ui-rewindbutton").addClass("bmpui-hidden");
let syncToggleHtml = "<span class='carbon-btn-synctoggle bmpui-ui-playbacktimelabel' style='cursor: pointer; min-width: 80px; position: relative; padding-left: 18px;'><span class='carbon-btn-synctoggle-label'>SYNC ON</span><div class='carbon-btn-synctoggle-dot' style='background-color: #56ff63; width: 8px; height: 8px; border-radius: 8px; position: absolute; left: 0; top: 5px;'></div></span>";
$(".carbon-player .bmpui-controlbar-top .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", syncToggleHtml);
$(".carbon-btn-synctoggle")[0].addEventListener("click", function () {
toggleSyncMode();
});
$(".carbon-player .bmpui-seekbar")[0].addEventListener("click", function () {
toggleSyncMode(0);
});
$(".carbon-player .bmpui-ui-rewindbutton")[0].addEventListener("mouseup", function () { //can't be listening to "click" event on rewind button, as we're simulating it elsewhere to fix sync mode
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-forwardbutton")[0].addEventListener("mouseup", function () {
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-playbacktogglebutton")[0].addEventListener("mouseup", function () {
toggleSyncMode(0);
})
$(".carbon-player .bmpui-ui-hugeplaybacktogglebutton")[0].addEventListener("mouseup", function () {
toggleSyncMode(0);
})
let syncDebugToggleHtml = "<div class='carbon-sync-debug-toggle bmpui-ui-settings-panel-item' style='cursor: pointer;' role='menuitem'><div class='bmpui-container-wrapper' style='cursor: pointer;'><label class='bmpui-ui-label' style='cursor: pointer;'>SYNC MODE DEBUG</label></div></div>";
$(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", syncDebugToggleHtml);
$(".carbon-sync-debug-toggle")[0].addEventListener("click", function () {
toggleSyncDebug();
})
let syncOffsetSwitcherHtml = "<span class='bmpui-ui-playbacktimelabel carbon-syncoffset-menu' style='font-size: 13px; line-height: 28px; padding-left: 14px; padding-right: 4px;'>Sync Offset:</span>" +
"<button class='carbon-btn-syncoffset-back bmpui-off carbon-syncoffset-menu' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + syncoffset_btn_image + ");' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Sync Offset (-)</span></button>" +
"<span class='bmpui-ui-playbacktimelabel carbon-syncoffset-view carbon-syncoffset-menu' style='font-size: 18px; line-height: 28px; padding: 0px 10px; min-width: 52px; text-align: center;'></span>" +
"<button class='carbon-btn-syncoffset-forward bmpui-off carbon-syncoffset-menu' style='background-color: transparent; background-origin: content-box; background-position: center; background-repeat: no-repeat; background-size: 1.5em; border: 0; -webkit-box-sizing: content-box; box-sizing: content-box; cursor: pointer; font-size: 1em; height: 1.5em; min-width: 1.5em; padding: 0.25em; background-image: url(" + syncoffset_btn_image + "); transform: rotate(180deg);' type='button' aria-pressed='false' tabindex='0' role='button'><span class='bmpui-label' style='display: none;'>Sync Offset (+)</span></button>";
$(".carbon-player .bmpui-container-wrapper .bmpui-ui-volumeslider")[0].insertAdjacentHTML("afterEnd", syncOffsetSwitcherHtml);
updateSyncOffsetView();
$(".carbon-player .carbon-btn-syncoffset-back").on("click", function () {
setSyncOffset(-200);
})
$(".carbon-player .carbon-btn-syncoffset-forward").on("click", function () {
setSyncOffset(+200);
})
}
let donateHtml = "<div class='carbon-sync-debug-toggle bmpui-ui-settings-panel-item' role='menuitem'><a style='color: #ff6643; text-align: center; display: block;' href='https://github.com/Carbon-for-F1TV/Carbon-for-F1TV/blob/master/DONATE.md' target='_blank'>❤ Donate to support Carbon for F1TV</a></div>";
$(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("beforeEnd", donateHtml);
}
function updateSyncOffsetView() {
let syncOffset = $("#sync-offset")[0].value || 0;
syncOffset = (Math.round(syncOffset) / 1000).toFixed(1);
$(".carbon-player .carbon-syncoffset-view").text(syncOffset);
}
function setSyncOffset(diff) {
let syncOffset = $("#sync-offset")[0].value || 0;
let newSyncOffset = parseInt(syncOffset) + diff;
$("#sync-offset")[0].value = newSyncOffset;
updateSyncOffsetView();
}
function toggleSyncDebug() {
if ($("#carbon-helper .sync-data").is(":visible")) {
$("#carbon-helper .sync-data").hide();
} else {
$("#carbon-helper .sync-data").show();
}
}
function toggleSyncMode(mode) {
let syncMode = parseInt(document.getElementById("sync-mode").value);
if (syncMode == 1 || mode == 0) {
log("sync mode disabled");
document.getElementById("sync-mode").value = 0;
$(".carbon-btn-synctoggle-label").text("SYNC OFF");
$(".carbon-btn-synctoggle-dot").css("background-color", "#999");
$(".carbon-syncoffset-menu").hide();
videoSpeed(1);
} else {
log("sync mode enabled");
document.getElementById("sync-mode").value = 1;
$(".carbon-btn-synctoggle-label").text("SYNC ON");
$(".carbon-btn-synctoggle-dot").css("background-color", "#56ff63");
$(".carbon-syncoffset-menu").show();
if ($(".carbon-player video")[0].paused) {
$(".carbon-player video")[0].play();
}
}
}
function waitForPlayer() {
if ($(".player-container.shown .bitmovinplayer-container").length > 0) {
if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) {
} else {
if ($(".player-container.shown .bitmovinplayer-container video")[0].readyState > 0) {
log("new player loaded");
injectPlayerFeatures();
// slow down loop
clearInterval(playerInjectLoop);
playerInjectLoop = setInterval(waitForPlayer, 1000);
}
}
} else {
//no player found
}
}
function waitForPageLoad() {
if ($("#app .app-wrapper").length > 0) {
clearInterval(waitForPageLoad);
$("#app")[0].insertAdjacentHTML("beforeEnd", "<div id='carbon-helper'></div>");
if (carbon_mode == "popout") {
let syncDataHtml = "<div class='sync-data'>" +
"<div>main window progress: <span id='main-progress'></span>, estimated realtime: <span class='thiswindow' id='main-progress-realtime'></span></div>" +
"<div>this window progress: <span class='thiswindow' id='this-progress-realtime'></span></div>" +
"<div>sync offset: <input id='sync-offset' type='number' step='250' value='' style='width: 80px;'>, target progress: <span class='thiswindow' id='this-progress-target'></span></div>" +
"<div>progress sync time: <span id='progress-sync-time'></span></div>" +
"<div>diff to target: <span class='thiswindow' id='diff-to-target'></span></div><br>" +
"<div>main window state: <span id='main-state'></span></div>" +
"<div>sync mode: <input id='sync-mode' type='number' step='1' value='1' style='width: 40px;'></div>" +
"<div>main window playback rate: <span id='main-playbackrate'></span></div>" +
"<div>this window playback rate: <span class='thiswindow' id='this-playbackrate'></span></div>" +
"</div>" +
"<style>" +
".sync-data {position: fixed; top: 0; left: 0; background-color: #000000bb; color: #aaa; padding: 6px; z-index: 1001; font-family: monospace; display: none;}" +
".sync-data span {font-weight: bold; color: #fff;}" +
".sync-data span.thiswindow {font-weight: bold; color: #ffff00;}" +
"</style>";
$("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", syncDataHtml);
}
}
}
var waitForPageLoad = setInterval(waitForPageLoad, 50);
var playerInjectLoop;
function pageChanged() {
clearInterval(playerInjectLoop);
if (document.location.href.split("formula1.com/")[1].split("/")[0] == "detail") {
log("Page changed (video)");
playerInjectLoop = setInterval(waitForPlayer, 50);
} else {
log("Page changed (not video)");
if (isTheaterMode()) {
toggleTheaterMode(true);
}
}
}
let oldHref = document.location.href;
let observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (oldHref != document.location.href) {
oldHref = document.location.href;
pageChanged();
}
});
});
observer.observe(document.querySelector("body"), {
childList: true,
subtree: true
});
pageChanged();
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="uuid-3364bd8f-48f6-4405-8529-206fd56ef5f0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.uuid-a395561c-aea9-4027-b57e-e9208b7a79c7{fill:#fff;}.uuid-f9f6e8f7-c3ae-4866-9241-b8aa91dbc594{fill:#cc2323;}.uuid-eff02059-9060-4e57-b26c-93a47698114d{fill:#872222;}</style></defs><path d="M445.56,256c0-25.6-5.01-50.42-14.89-73.78-9.55-22.57-23.22-42.85-40.63-60.26-17.41-17.41-37.69-31.08-60.26-40.63-23.36-9.88-48.18-14.89-73.78-14.89s-50.42,5.01-73.78,14.89c-22.57,9.55-42.85,23.22-60.26,40.63-17.41,17.41-31.08,37.69-40.63,60.26-9.88,23.36-14.89,48.18-14.89,73.78s5.01,50.42,14.89,73.78c9.55,22.57,23.22,42.85,40.63,60.26,17.41,17.41,37.69,31.08,60.26,40.63,23.36,9.88,48.18,14.89,73.78,14.89s50.42-5.01,73.78-14.89c22.57-9.55,42.85-23.22,60.26-40.63,17.41-17.41,31.08-37.69,40.63-60.26,9.88-23.36,14.89-48.18,14.89-73.78Zm-189.56,148.07c-81.78,0-148.07-66.29-148.07-148.07S174.22,107.93,256,107.93s148.07,66.29,148.07,148.07-66.29,148.07-148.07,148.07Z"/><path d="M256,0C114.62,0,0,114.62,0,256s114.62,256,256,256,256-114.62,256-256S397.38,0,256,0Zm0,15.61c62.24-.45,124.76,25.44,168.4,68.84l-10.51,10.7c-42.4-41.62-98.47-64.54-157.89-64.54-60.52-.23-119.43,24.71-161.47,68.14l-10.74-10.47C128.48,42.13,191.35,15.37,256,15.61Zm-90.98,462.97c-74.65-30.16-131.04-99-145.37-178.47l14.75-2.74c13.19,74.02,66.56,139.32,136.3,167.32l-5.68,13.88ZM51.44,256c0-112.98,91.59-204.56,204.56-204.56s204.56,91.59,204.56,204.56-91.59,204.56-204.56,204.56S51.44,368.98,51.44,256Zm300.53,220.47l-5.99-13.75c67.2-29.29,116.1-89.52,130.81-161.12l14.69,3.02c-15.25,75.69-68.38,141.12-139.51,171.85Z"/><path class="uuid-f9f6e8f7-c3ae-4866-9241-b8aa91dbc594" d="M460.56,256c0-112.98-91.59-204.56-204.56-204.56S51.44,143.02,51.44,256s91.59,204.56,204.56,204.56,204.56-91.59,204.56-204.56Zm-130.78,174.67c-23.36,9.88-48.18,14.89-73.78,14.89s-50.42-5.01-73.78-14.89c-22.57-9.55-42.85-23.22-60.26-40.63-17.41-17.41-31.08-37.69-40.63-60.26-9.88-23.36-14.89-48.18-14.89-73.78s5.01-50.42,14.89-73.78c9.55-22.57,23.22-42.85,40.63-60.26,17.41-17.41,37.69-31.08,60.26-40.63,23.36-9.88,48.18-14.89,73.78-14.89s50.42,5.01,73.78,14.89c22.57,9.55,42.85,23.22,60.26,40.63,17.41,17.41,31.08,37.69,40.63,60.26,9.88,23.36,14.89,48.18,14.89,73.78s-5.01,50.42-14.89,73.78c-9.55,22.57-23.22,42.85-40.63,60.26-17.41,17.41-37.69,31.08-60.26,40.63Z"/><path class="uuid-eff02059-9060-4e57-b26c-93a47698114d" d="M256,107.93c-81.78,0-148.07,66.29-148.07,148.07s66.29,148.07,148.07,148.07,148.07-66.29,148.07-148.07-66.29-148.07-148.07-148.07Zm-.06,228.4c21.32,0,40.72-8.35,55.12-21.95h44.44c-20.08,34.12-57.19,57.06-99.56,57.06-63.66,0-115.44-51.79-115.44-115.44s51.79-115.44,115.44-115.44c42.37,0,79.47,22.95,99.56,57.06h-44.44c-14.4-13.6-33.8-21.95-55.12-21.95-44.29,0-80.33,36.03-80.33,80.33s36.03,80.33,80.33,80.33Z"/><path class="uuid-a395561c-aea9-4027-b57e-e9208b7a79c7" d="M255.94,175.67c21.32,0,40.72,8.35,55.12,21.95h44.44c-20.08-34.12-57.19-57.06-99.56-57.06-63.66,0-115.44,51.79-115.44,115.44s51.79,115.44,115.44,115.44c42.37,0,79.47-22.95,99.56-57.06h-44.44c-14.4,13.6-33.8,21.95-55.12,21.95-44.29,0-80.33-36.03-80.33-80.33s36.03-80.33,80.33-80.33Z"/><path class="uuid-a395561c-aea9-4027-b57e-e9208b7a79c7" d="M34.4,297.37l-14.75,2.74c14.33,79.47,70.71,148.31,145.37,178.47l5.68-13.88c-69.74-28-123.11-93.3-136.3-167.32Z"/><path class="uuid-a395561c-aea9-4027-b57e-e9208b7a79c7" d="M256,30.61c59.42,0,115.49,22.92,157.89,64.54l10.51-10.7C380.76,41.04,318.24,15.16,256,15.61c-64.65-.24-127.52,26.52-172.21,72.67l10.74,10.47C136.57,55.32,195.48,30.38,256,30.61Z"/><path class="uuid-a395561c-aea9-4027-b57e-e9208b7a79c7" d="M476.78,301.6c-14.71,71.6-63.61,131.83-130.81,161.12l5.99,13.75c71.12-30.74,124.26-96.16,139.51-171.85l-14.69-3.02Z"/></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+2
View File
@@ -0,0 +1,2 @@
/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}function fe(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}ce.fn=ce.prototype={jquery:t,constructor:ce,length:0,toArray:function(){return ae.call(this)},get:function(e){return null==e?ae.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=ce.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return ce.each(this,e)},map:function(n){return this.pushStack(ce.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(ae.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ce.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(ce.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:oe.sort,splice:oe.splice},ce.extend=ce.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||v(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(ce.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||ce.isPlainObject(n)?n:{},i=!1,a[t]=ce.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},ce.extend({expando:"jQuery"+(t+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==i.call(e))&&(!(t=r(e))||"function"==typeof(n=ue.call(t,"constructor")&&t.constructor)&&o.call(n)===a)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){m(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(c(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},text:function(e){var t,n="",r=0,i=e.nodeType;if(!i)while(t=e[r++])n+=ce.text(t);return 1===i||11===i?e.textContent:9===i?e.documentElement.textContent:3===i||4===i?e.nodeValue:n},makeArray:function(e,t){var n=t||[];return null!=e&&(c(Object(e))?ce.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},isXMLDoc:function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!l.test(t||n&&n.nodeName||"HTML")},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(c(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:le}),"function"==typeof Symbol&&(ce.fn[Symbol.iterator]=oe[Symbol.iterator]),ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var pe=oe.pop,de=oe.sort,he=oe.splice,ge="[\\x20\\t\\r\\n\\f]",ve=new RegExp("^"+ge+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ge+"+$","g");ce.contains=function(e,t){var n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(e.contains?e.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))};var f=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function p(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}ce.escapeSelector=function(e){return(e+"").replace(f,p)};var ye=C,me=s;!function(){var e,b,w,o,a,T,r,C,d,i,k=me,S=ce.expando,E=0,n=0,s=W(),c=W(),u=W(),h=W(),l=function(e,t){return e===t&&(a=!0),0},f="checked|selected|async|autofocus|autoplay|conLine truncated
+32
View File
@@ -0,0 +1,32 @@
{
"manifest_version": 3,
"name": "Carbon for F1TV",
"description": "Enhance your F1TV experience.",
"version": "1.0.1",
"icons": {
"16": "icon/16.png",
"48": "icon/48.png",
"128": "icon/128.png",
"512": "icon/512.png"
},
"content_scripts": [
{
"matches": [
"https://f1tv.formula1.com/*"
],
"js": [
"lib/jquery-3.7.1.min.js",
"carbon-for-f1tv.js"
]
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon/16.png",
"48": "icon/48.png",
"128": "icon/128.png",
"512": "icon/512.png"
}
}
}
+37
View File
@@ -0,0 +1,37 @@
{
"manifest_version": 2,
"name": "Carbon for F1TV",
"description": "Enhance your F1TV experience.",
"version": "1.0.1",
"icons": {
"16": "icon/16.png",
"48": "icon/48.png",
"128": "icon/128.png",
"512": "icon/512.png"
},
"permissions": [
"https://f1tv.formula1.com/*"
],
"content_scripts":[
{
"matches":[
"https://f1tv.formula1.com/*"
],
"js": ["lib/jquery-3.7.1.min.js", "carbon-for-f1tv.js"]
}
],
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon/16.png",
"48": "icon/48.png",
"128": "icon/128.png",
"512": "icon/512.png"
}
},
"browser_specific_settings": {
"gecko": {
"id": "carbon-for-f1tv@carbon-for-f1tv"
}
}
}
+130
View File
@@ -0,0 +1,130 @@
<html>
<head>
<title>Carbon for F1TV</title>
<script src="lib/jquery-3.7.1.min.js"></script>
<style>
body {
background-color: #3d0000;
color: #eee;
font-size: 18px;
font-family: sans-serif;
width: 385px;
height: 580px;
margin: 0;
}
.carbon-header {
display: flex;
justify-content: center;
align-items: center;
background-color: #3d0000;
padding: 6px 0px;
}
.carbon-header .logo {
background-image: url("icon/icon.svg");
background-repeat: no-repeat;
width: 32px;
height: 32px;
}
.carbon-header .title {
font-size: 22px;
margin-left: 12px;
}
.content {
font-size: 18px;
}
.carbon-video {
display: block;
background-color: #00000085;
border-radius: 18px;
overflow: hidden;
margin: 8px 4px;
cursor: pointer;
}
.carbon-video:hover {
background-color: #0000004c;
}
.carbon-video .video-content {
display: flex;
justify-content: center;
align-items: center;
}
.video-content .video-img {
width: 167px;
height: 83.5px;
background-repeat: no-repeat;
background-size: cover;
display: block;
position: relative;
}
.video-content .video-img .video-duration {
position: absolute;
bottom: 0;
right: 0;
background-color: #0000009e;
color: #eee;
padding: 4px;
font-size: 13px;
}
.video-content .video-title {
word-break: break-word;
display: block;
font-size: 15px;
color: #eee;
width: 190px;
padding: 4px 8px;
}
a:link {
text-decoration: none;
}
a:visited {
text-decoration: none;
}
a:hover {
text-decoration: none;
}
a:active {
text-decoration: none;
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background-color: #00000029;
}
::-webkit-scrollbar-thumb {
background: rgb(118, 15, 15);
border-radius: 10px;
}
</style>
</head>
<body>
<div class="carbon-header">
<div class="logo"></div>
<div class="title">Carbon for F1TV</div>
</div>
<div class="content">
<div id="video-list"></div>
</div>
<script src="popup.js"></script>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
var f1tvDomain = "https://f1tv.formula1.com/";
var f1tvVideosUrl = "2.0/R/ENG/WEB_DASH/ALL/PAGE/SEARCH/VOD/F1_TV_Pro_Annual/2?orderBy=contractStartDate&sortOrder=desc&filter_orderByDefault=Y&maxResults=18";
var f1tvVideoImageUrl = [f1tvDomain + "image-resizer/image/",
"?w=296&h=167&q=HI&o=L"];
var f1tvVideos = $.getJSON(f1tvDomain + f1tvVideosUrl, function (data) {
if (data.resultCode == "OK") {
listVideos(data.resultObj.containers);
}
}).fail(function() {
$("#video-list").text("Error: Can't load video list from F1TV");
});
function listVideos(data) {
for (let i = 0; i < data.length; i++) {
let videoHtml = "<div class='carbon-video' id='vid-" + i + "'>" +
"<div class='video-content'>" +
"<div class='video-img' id='video-img-" + i + "'><div class='video-duration'></div></div>" +
"<div class='video-title'></div>" +
"</div>" +
"</div>";
$("#video-list")[0].insertAdjacentHTML("beforeend", videoHtml);
$("#vid-" + i + " .video-img").css("background-image", "url(" + f1tvVideoImageUrl[0] + data[i]["metadata"]["pictureUrl"] + f1tvVideoImageUrl[1] + ")");
$("#vid-" + i + " .video-title").text(data[i]["metadata"]["title"]);
let duration = data[i]["metadata"]["uiDuration"];
let durationH = parseInt(duration.split(":")[0]);
let durationM;
let durationS;
if (durationH > 0) {
durationM = duration.split(":")[1];
durationS = duration.split(":")[2];
duration = durationH + ":" + durationM + ":" + durationS;
} else {
durationM = parseInt(duration.split(":")[1]);
durationS = duration.split(":")[2];
duration = durationM + ":" + durationS;
}
$("#vid-" + i + " .video-duration").text(duration);
$("#vid-" + i)[0].addEventListener("click", function () {
window.open(f1tvDomain + "detail/" + data[i]["metadata"]["contentId"] + "/a");
window.close();
})
}
}