From e810acc0b94de61fb12efe5f98c1171d97e05d4c Mon Sep 17 00:00:00 2001 From: Mateusz Najdek Date: Fri, 23 Feb 2024 15:19:30 +0100 Subject: [PATCH] New feature: Live latency target --- extension/carbon-for-f1tv.user.js | 274 ++++++++++++++++++++++++++++-- 1 file changed, 261 insertions(+), 13 deletions(-) diff --git a/extension/carbon-for-f1tv.user.js b/extension/carbon-for-f1tv.user.js index b90791a..0026933 100644 --- a/extension/carbon-for-f1tv.user.js +++ b/extension/carbon-for-f1tv.user.js @@ -10,6 +10,8 @@ var DEFAULT_THEATERMODE = true; +var DEFAULT_LATENCY = 30; + var theatermode_btn_image = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIGlkPSdMYXllcl8xJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4PScwcHgnIHk9JzBweCcgdmlld0JveD0nMCAwIDI4My41IDQyNS4yJyBzdHlsZT0nZW5hYmxlLWJhY2tncm91bm" + @@ -188,7 +190,7 @@ if (carbon_mode == "popout") { let diffToTargetAbs = Math.abs(diffToTarget); if (diffToTargetAbs > 15) { - videoJumpToProgress(targetProgress); + videoSyncJumpToProgress(targetProgress); } else if (diffToTargetAbs > 10) { if (diffToTarget > 0) { videoSpeed(3); } else { videoSpeed(0.333); } } else if (diffToTargetAbs > 5) { @@ -202,11 +204,94 @@ if (carbon_mode == "popout") { } else { videoSpeed(1); } + + if (diffToTargetAbs > 0.2) { + $(".sync-toppopup").show(); + $(".sync-toppopup-diff").text(diffToTarget.toFixed(2) + "s"); + $(".sync-toppopup-speed").text(thisPlaybackrate.toFixed(2) + "x"); + } else { + $(".sync-toppopup").hide(); + } } }, 500); +} else { + + var latencyModeData = setInterval(function () { + if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) { + let thisProgress = $(".carbon-player video")[0].currentTime; + $("#this-progress").text(thisProgress); + + let thisPlaybackrate = $(".carbon-player video")[0].playbackRate; + $("#this-playbackrate").text(thisPlaybackrate); + + let latencyTarget = parseInt(document.getElementById("latencymode-latency").value) / 1000 || DEFAULT_LATENCY; + let targetProgress = (Date.now() / 1000) - latencyTarget; + $("#this-targetprogress").text(targetProgress); + + let diffToTarget = targetProgress - thisProgress; + let diffToTargetAbs = Math.abs(diffToTarget); + $("#diff-to-target").text(diffToTarget); + + let state; + if ($(".carbon-player video")[0].paused) { + state = "paused"; + } else { + state = "playing"; + } + + $("#this-state").text(state); + + + + //let diffToTarget = targetProgress - thisProgressRealtime; + //$("#diff-to-target").text(diffToTarget); + } else { + log("can't sync to latency, player not loaded"); + } + }, 500); + + + let syncNow = setInterval(function () { + let latencyMode = parseInt(document.getElementById("latency-mode").value); + if (latencyMode == 1) { + let thisProgress = parseFloat($("#this-progress").text()); + let targetProgress = parseFloat($("#this-targetprogress").text()); + let thisPlaybackrate = parseFloat($("#this-playbackrate").text()); + let diffToTarget = targetProgress - thisProgress; + let diffToTargetAbs = Math.abs(diffToTarget); + + if (diffToTargetAbs > 15) { + videoLatencyJumpToProgress(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); + } + + if (diffToTargetAbs > 0.2) { + $(".sync-toppopup").show(); + $(".sync-toppopup-diff").text(diffToTarget.toFixed(2) + "s"); + $(".sync-toppopup-speed").text(thisPlaybackrate.toFixed(2) + "x"); + } else { + $(".sync-toppopup").hide(); + } + } + }, 500); + + } + + function videoSpeed(speed) { if ($(".player-container.shown .bitmovinplayer-container.carbon-player").length > 0) { $(".carbon-player video")[0].playbackRate = speed; @@ -216,7 +301,7 @@ function videoSpeed(speed) { } -function videoJumpToProgress(progress) { +function videoSyncJumpToProgress(progress) { let oldTime = $(".carbon-player video")[0].currentTime; let newTime = progress + 10; $(".carbon-player video")[0].currentTime = newTime; @@ -231,7 +316,7 @@ function videoJumpToProgress(progress) { // for some reason, on live streams we can't jump forward by much, // this simulates clicking "live" button and enables sync again to jump backward $(".bmpui-ui-playbacktimelabel-live")[0].click(); - setTimeout(function() { + setTimeout(function () { toggleSyncMode(1); }, 1000); } @@ -244,6 +329,38 @@ function videoJumpToProgress(progress) { } +function videoLatencyJumpToProgress(progress) { + let oldTime = $(".carbon-player video")[0].currentTime; + if (oldTime < 1700000000) { + log("not live, or something is wrong. Disabling latency mode"); + toggleLatencyMode(0); + } + let newTime = progress + 10; + $(".carbon-player video")[0].currentTime = newTime; + let checkTime = $(".carbon-player video")[0].currentTime; + log("Jumped from: " + oldTime + ", to: " + newTime + ". Current time:" + checkTime); + if (Math.abs(checkTime - newTime) > 15) { + log("problem with sync, disabling"); + toggleLatencyMode(0); + $(".carbon-player video")[0].pause(); + if ($(".bmpui-ui-playbacktimelabel-live").length > 0) { + log("attempting to fix latency mode sync on live stream"); + // for some reason, on live streams we can't jump forward by much, + // this simulates clicking "live" button and enables sync again to jump backward + $(".bmpui-ui-playbacktimelabel-live")[0].click(); + setTimeout(function () { + toggleLatencyMode(1); + }, 1000); + } + } else { + // Simulates click on seek-backward button. + // This fixes Bitmovin player sometimes not buffering video after setting it's currentTime value. + $(".carbon-player video")[0].currentTime = newTime; + $(".carbon-player .bmpui-ui-rewindbutton")[0].click(); + } +} + + function injectPlayerFeatures() { log("Injecting player features"); @@ -311,6 +428,74 @@ function injectPlayerFeatures() { }) } + if ((carbon_mode !== "popout") && (is_live == true)) { + + + let latencyDebugToggleHtml = ""; + $(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", latencyDebugToggleHtml); + $(".carbon-latency-debug-toggle")[0].addEventListener("click", function () { + toggleLatencyModeDebug(); + }) + + + $(".carbon-player .bmpui-seekbar")[0].addEventListener("click", function () { + toggleLatencyMode(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 + toggleLatencyMode(0); + }); + $(".carbon-player .bmpui-ui-forwardbutton")[0].addEventListener("mouseup", function () { + toggleLatencyMode(0); + }); + $(".carbon-player .bmpui-ui-playbacktogglebutton")[0].addEventListener("mouseup", function () { + toggleLatencyMode(0); + }); + $(".carbon-player .bmpui-ui-hugeplaybacktogglebutton")[0].addEventListener("mouseup", function () { + toggleLatencyMode(0); + }); + + + + function setLatency(latency) { + let oldLatency = parseInt($("#latencymode-latency")[0].value) || DEFAULT_LATENCY * 1000; + let newLatency = oldLatency + latency; + $("#latencymode-latency")[0].value = newLatency; + $(".carbon-targetlatency-view").text((newLatency / 1000).toFixed(1)); + log("Setting live latency by: " + latency + ". New latency: " + newLatency); + } + + + let targetLatencyMenuHtml = "
" + + "" + + "" + + "" + + "" + + "" + + "
"; + + + $(".carbon-player .bmpui-container-wrapper .bmpui-ui-volumeslider")[0].insertAdjacentHTML("afterEnd", targetLatencyMenuHtml); + + $(".carbon-player .carbon-btn-latency-back-10").on("click", function () { + setLatency(-1000); + }); + $(".carbon-player .carbon-btn-latency-back-01").on("click", function () { + setLatency(-100); + }); + $(".carbon-player .carbon-btn-latency-forward-01").on("click", function () { + setLatency(+100); + }); + $(".carbon-player .carbon-btn-latency-forward-10").on("click", function () { + setLatency(+1000); + }); + $(".carbon-player .carbon-targetlatency-menu-disable").on("click", function () { + toggleLatencyMode(0); + }); + $(".carbon-player .carbon-targetlatency-menu-enable").on("click", function () { + toggleLatencyMode(1); + }); + } + if (carbon_mode == "popout") { $(".carbon-player .bmpui-ui-casttogglebutton").addClass("bmpui-hidden"); //$(".carbon-player .bmpui-ui-forwardbutton").addClass("bmpui-hidden"); @@ -327,22 +512,22 @@ function injectPlayerFeatures() { }); $(".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 = ""; $(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("afterbegin", syncDebugToggleHtml); $(".carbon-sync-debug-toggle")[0].addEventListener("click", function () { toggleSyncDebug(); - }) + }); let syncOffsetSwitcherHtml = "Sync Offset:" + "" + @@ -352,14 +537,16 @@ function injectPlayerFeatures() { 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 = ""; + + + let donateHtml = ""; $(".bmpui-ui-settings-panel-page .bmpui-container-wrapper")[0].insertAdjacentHTML("beforeEnd", donateHtml); @@ -390,14 +577,18 @@ function toggleSyncDebug() { function toggleSyncMode(mode) { let syncMode = parseInt(document.getElementById("sync-mode").value); + if (syncMode == mode) { + return; + } 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(); - setTimeout(function() { - videoSpeed(1); + $(".sync-toppopup").hide(); + setTimeout(function () { + videoSpeed(1); }, 1000); } else { log("sync mode enabled"); @@ -411,6 +602,41 @@ function toggleSyncMode(mode) { } } +function toggleLatencyModeDebug() { + if ($("#carbon-helper .latencymode-data").is(":visible")) { + $("#carbon-helper .latencymode-data").hide(); + } else { + $("#carbon-helper .latencymode-data").show(); + } +} + +function toggleLatencyMode(mode) { + let latencyMode = parseInt(document.getElementById("latency-mode").value); + if (latencyMode == mode) { + return; + } + if (latencyMode == 1 || mode == 0) { + log("latency mode disabled"); + document.getElementById("latency-mode").value = 0; + $(".carbon-targetlatency-menu").hide(); + $(".carbon-targetlatency-menu-enable").show(); + $(".sync-toppopup").hide(); + setTimeout(function () { + videoSpeed(1); + }, 1000); + } else if (parseInt($("#this-progress").text) < 1700000000) { + log("can't enable latency mode. video isn't live?"); + } else { + log("latency mode enabled"); + document.getElementById("latency-mode").value = 1; + $(".carbon-targetlatency-menu").show(); + $(".carbon-targetlatency-menu-enable").hide(); + 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) { @@ -455,7 +681,29 @@ function waitForPageLoad() { ".sync-data span.thiswindow {font-weight: bold; color: #ffff00;}" + ""; $("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", syncDataHtml); + } else { + let latencyModeDataHtml = "
" + + "
video progress:
" + + "
latency: , target progress:
" + + "
diff to target:

" + + "
state:
" + + + "
latency mode:
" + + "
playback rate:
" + + "
" + + ""; + $("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", latencyModeDataHtml); } + let syncTopPopupHtml = ""; + $("#carbon-helper")[0].insertAdjacentHTML("beforeEnd", syncTopPopupHtml); + } }