Enhancing Trunk Recorder with Tampermonkey Userscripts
I prompted ChatGPT’s new model o1-preview into making a working Userscript to create a playback speed button
and another to automatically expand all new calls in the case that you use transcription.
Installation
You will first need to install the Tampermonkey extension for your browser of choice. Firefox and Chrome are supported,
and probably Opera and others as well.
This also works on Firefox for Mobile.
Configuration
You will need to edit the scripts to point to your own instance of Trunk Recorder. Mine is at 192.168.124.207
;
The part that says: // @match *://192.168.124.207
is where you insert the address to the webserver
Scripts
Script #1 (updated, and working with firefox too): Implementing Speed Changing for Recordings
Click to reveal the script code
// ==UserScript==
// @name Trunk Recorder Dynamic Playback Speed Control with Scrollable Menu
// @namespace http://tampermonkey.net/
// @version 1.9
// @description Adds a dynamic speed control button with a scrollable menu from 0.25x to 10x increments.
// @author
// @match *://192.168.124.207/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Default playback speed set to 1x
let savedSpeed = parseFloat(localStorage.getItem('playbackSpeed') || '1.0');
// Function to set playback speed on an audio element
function setAudioPlaybackSpeed(audio, speed) {
if (audio) {
audio.playbackRate = speed;
audio.addEventListener('loadedmetadata', () => {
audio.playbackRate = speed;
});Auto Expand CallText
}
}
// Apply the playback speed to all audio elements
function applyPlaybackSpeedToAll(speed) {
const audioElements = document.querySelectorAll('audio');
audioElements.forEach(audio => {
setAudioPlaybackSpeed(audio, speed);
});
}
// Function to dynamically generate speed options based on your logic
function generateSpeedOptions() {
const speeds = [];
for (let i = 0.25; i <= 10; i += 0.1) {
const rounded = (Math.round(i * 100) / 100).toFixed(2); // Keep two decimal places
const displayValue = (rounded % 1 === 0.25 || rounded % 1 === 0.75) ? parseFloat(rounded) : parseFloat(i.toFixed(1));
speeds.push(displayValue);
}
return [...new Set(speeds)]; // Remove duplicates and return unique values
}
// Initialize the speed control button
function initSpeedControl() {
applyPlaybackSpeedToAll(savedSpeed);
const targetElement = document.querySelector('#searchcontent .headeritem#playbackcontrols');
if (!targetElement) {
console.error('Target element for speed control not found.');
return;
}
const speedControlContainer = document.createElement('div');
speedControlContainer.id = 'speedcontrolcontainer';
speedControlContainer.className = 'headeritem';
const speedButtonWrapper = document.createElement('div');
speedButtonWrapper.className = 'headeritem searchcheckbox ui-controlgroup ui-controlgroup-horizontal ui-helper-clearfix';
speedButtonWrapper.setAttribute('role', 'toolbar');
const speedButton = document.createElement('button');
speedButton.id = 'speedcontrol';
speedButton.className = 'ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only';
speedButton.type = 'button';
const buttonContent = document.createElement('span');
buttonContent.className = 'ui-button-text';
buttonContent.textContent = `${savedSpeed}x`;
speedButton.appendChild(buttonContent);
speedButtonWrapper.appendChild(speedButton);
speedButton.addEventListener('click', (event) => {
event.stopPropagation();
showSpeedOptions(speedButton);
});
speedControlContainer.appendChild(speedButtonWrapper);
targetElement.parentNode.insertBefore(speedControlContainer, targetElement.nextSibling);
}
// Display speed options
function showSpeedOptions(button) {
const existingMenu = document.querySelector('#speed-options-menu');
if (existingMenu) {
existingMenu.remove();
return;
}
const menu = document.createElement('div');
menu.id = 'speed-options-menu';
menu.style.position = 'absolute';
menu.style.backgroundColor = '#333';
menu.style.border = '1px solid #555';
menu.style.padding = '5px';
menu.style.zIndex = '10000';
menu.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
menu.style.fontFamily = 'Arial, sans-serif';
menu.style.color = '#fff';
menu.style.maxHeight = '200px'; // Set a fixed height
menu.style.overflowY = 'scroll'; // Enable vertical scrolling
const rect = button.getBoundingClientRect();
menu.style.left = `${rect.left + window.scrollX}px`;
menu.style.top = `${rect.bottom + window.scrollY}px`;
const speeds = generateSpeedOptions();
speeds.forEach(speed => {
const option = document.createElement('div');
option.textContent = `${speed}x`;
option.style.padding = '5px';
option.style.cursor = 'pointer';
if (speed === savedSpeed) {
option.style.fontWeight = 'bold';
option.style.backgroundColor = '#555';
}
option.addEventListener('mouseenter', () => {
option.style.backgroundColor = '#666';
});
option.addEventListener('mouseleave', () => {
option.style.backgroundColor = speed === savedSpeed ? '#555' : '';
});
option.addEventListener('click', (e) => {
e.stopPropagation();
savedSpeed = speed;
localStorage.setItem('playbackSpeed', speed);
applyPlaybackSpeedToAll(speed);
button.querySelector('.ui-button-text').textContent = `${speed}x`;
menu.remove();
});
menu.appendChild(option);
});
document.body.appendChild(menu);
function handleClickOutside(event) {
if (!menu.contains(event.target) && event.target !== button) {
menu.remove();
document.removeEventListener('click', handleClickOutside);
}
}
document.addEventListener('click', handleClickOutside);
}
// Observe new audio elements in the DOM
function observeNewAudioElements() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.tagName === 'AUDIO') {
setAudioPlaybackSpeed(node, savedSpeed);
} else if (node.querySelectorAll) {
node.querySelectorAll('audio').forEach(audio => {
setAudioPlaybackSpeed(audio, savedSpeed);
});
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
function init() {
initSpeedControl();
applyPlaybackSpeedToAll(savedSpeed);
observeNewAudioElements();
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
Script #2 (updated v2, faster, much simpler code and works in firefox too): Script for automatically expanding each tab as soon as a transcript is detected:
Click to reveal the script code
// ==UserScript== // @name Trunk Recorder Auto Expand CallText Rows (Polling + Event Simulation) // @namespace http://tampermonkey.net/ // @version 1.6 // @description Automatically clicks and expands rows using polling for compatibility with Firefox and Chrome. // @author You // @match ://192.168.124.207/ // @grant none // ==/UserScript==
(function() { ‘use strict’;
// Polls and simulates clicks on CallText elements
function expandCallTextRows() {
document.querySelectorAll('td.dt-control.CallText:not(.expanded)').forEach((element) => {
if (!element.closest('tr').nextElementSibling?.querySelector('td[colspan="14"]')) {
element.click(); // Simulate click
element.classList.add('expanded'); // Prevent re-clicking
}
});
}
// Poll for new rows every 2 seconds
setInterval(expandCallTextRows, 2000);
})();
Have fun!