You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, trackTitle = "Original Ringtone", artistName = "Android Telephone", theme = "dark") {
// Determine theme colors
const isDark = theme.toLowerCase() !== "light";
const bgColor = isDark ? '#121212' : '#f0f0f0';
const cardBgColor = isDark ? '#1e1e1e' : '#ffffff';
const txtColor = isDark ? '#ffffff' : '#222222';
const subTxtColor = isDark ? '#aaaaaa' : '#666666';
const accentColor = '#1db954'; // Classic music player green
// Import Android-style font (Google Fonts Roboto)
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);
// Create Main Container Wrapper
const container = document.createElement('div');
const containerId = 'player-ui-' + Math.random().toString(36).substring(2, 9);
container.id = containerId;
container.style.width = '100%';
container.style.maxWidth = '360px'; // Android phone width
container.style.margin = '0 auto';
container.style.backgroundColor = cardBgColor;
container.style.borderRadius = '24px';
container.style.boxShadow = isDark ? '0 15px 35px rgba(0,0,0,0.5)' : '0 15px 35px rgba(0,0,0,0.1)';
container.style.overflow = 'hidden';
container.style.fontFamily = "'Roboto', sans-serif";
container.style.display = 'flex';
container.style.flexDirection = 'column';
// Inject Scoped Styles for interactivity and UI aesthetics
const style = document.createElement('style');
style.innerHTML = `
#${containerId} * {
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
#${containerId} .btn-play-pause {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
#${containerId} .btn-play-pause:hover {
transform: scale(1.05);
box-shadow: 0 8px 20px rgba(29, 185, 84, 0.5);
}
#${containerId} .btn-play-pause:active {
transform: scale(0.95);
}
#${containerId} .ctrl-btn {
transition: opacity 0.2s;
cursor: pointer;
}
#${containerId} .ctrl-btn:hover {
opacity: 0.7;
}
#${containerId} .progress-bar {
cursor: pointer;
}
`;
container.appendChild(style);
// Construct Player HTML Body
const playerUI = document.createElement('div');
playerUI.innerHTML = `
<div style="padding: 24px 24px 16px; text-align: center; color: ${subTxtColor}; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;">
Now Playing from MP3
</div>
<!-- Album Art -->
<div style="padding: 0 30px;">
<div class="art-container" style="width: 100%; aspect-ratio: 1; margin: 0 auto; border-radius: 20px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.4); background: ${bgColor}; display: flex; align-items: center; justify-content: center;">
</div>
</div>
<!-- Track Info -->
<div style="padding: 24px 30px 16px; text-align: center;">
<h2 style="margin: 0; color: ${txtColor}; font-size: 24px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2;">
${trackTitle}
</h2>
<h3 style="margin: 6px 0 0; color: ${subTxtColor}; font-size: 15px; font-weight: 400; line-height: 1.2;">
${artistName}
</h3>
</div>
<!-- Progress/Seekbar -->
<div style="padding: 0 30px 16px;">
<div class="progress-bar" style="width: 100%; height: 5px; background: ${isDark ? '#444' : '#e0e0e0'}; border-radius: 3px; position: relative; margin-top: 10px;">
<div class="progress-fill" style="width: 0%; height: 100%; background: ${accentColor}; border-radius: 3px; pointer-events: none;"></div>
<div class="progress-thumb" style="width: 14px; height: 14px; background: ${txtColor}; border-radius: 50%; position: absolute; top: 50%; left: 0%; transform: translate(-50%, -50%); box-shadow: 0 2px 5px rgba(0,0,0,0.3); pointer-events: none;"></div>
</div>
<div style="display: flex; justify-content: space-between; margin-top: 8px; color: ${subTxtColor}; font-size: 12px; font-weight: 400;">
<span class="time-current">0:00</span>
<span class="time-total">0:30</span>
</div>
</div>
<!-- Controls -->
<div style="padding: 0 30px 32px; display: flex; justify-content: space-between; align-items: center;">
<div class="ctrl-btn" style="color: ${txtColor}; font-size: 28px; line-height: 1; transform: scaleX(-1);">⏭</div>
<button class="btn-play-pause" style="width: 64px; height: 64px; border-radius: 50%; background: ${accentColor}; border: none; color: #fff; font-size: 26px; display: flex; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 6px 15px rgba(29, 185, 84, 0.3); padding-left: 4px; outline: none;">
▶
</button>
<div class="ctrl-btn" style="color: ${txtColor}; font-size: 28px; line-height: 1;">⏭</div>
</div>
`;
container.appendChild(playerUI);
// Attach Photo (Original Image)
const img = new Image();
img.src = originalImg.src;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
container.querySelector('.art-container').appendChild(img);
// Audio & State Logic
let isPlaying = false;
let audioCtx = null;
let activeOscillators = [];
let loopTimeout = null;
let progressInterval = null;
let currentTime = 0;
const totalTime = 30; // Simulated track runtime in seconds for the ringtone loop
const btnPlay = container.querySelector('.btn-play-pause');
const fill = container.querySelector('.progress-fill');
const thumb = container.querySelector('.progress-thumb');
const timeCurrentEl = container.querySelector('.time-current');
function formatTime(s) {
const min = Math.floor(s / 60);
const sec = Math.floor(s % 60);
return `${min}:${sec < 10 ? '0' : ''}${sec}`;
}
// Safely stop background Web Audio API components
function stopAudio() {
if (loopTimeout) clearTimeout(loopTimeout);
activeOscillators.forEach(osc => {
try { osc.stop(); } catch (e) { }
});
activeOscillators = [];
}
// Synthesize MP3 Ringtone player using simple oscillator sequences (Classic Digital Style)
function playRingtoneSequence() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
// Classic Telephone / Monophonic Melodic Sequence
const melody = [
{ f: 1318.51, d: 0.15 }, { f: 1174.66, d: 0.15 }, { f: 739.99, d: 0.30 }, { f: 830.61, d: 0.30 },
{ f: 1108.73, d: 0.15 }, { f: 987.77, d: 0.15 }, { f: 587.33, d: 0.30 }, { f: 659.25, d: 0.30 },
{ f: 987.77, d: 0.15 }, { f: 880.00, d: 0.15 }, { f: 554.37, d: 0.30 }, { f: 659.25, d: 0.30 },
{ f: 880.00, d: 0.80 }
];
let startTime = audioCtx.currentTime;
stopAudio();
melody.forEach(note => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle'; // Gives a slightly retro electronic feel
osc.frequency.value = note.f;
osc.connect(gain);
gain.connect(audioCtx.destination);
// Envelope to prevent loud clicks and harsh edges
gain.gain.setValueAtTime(0, startTime);
gain.gain.linearRampToValueAtTime(0.12, startTime + 0.02);
gain.gain.setValueAtTime(0.12, startTime + note.d - 0.03);
gain.gain.linearRampToValueAtTime(0, startTime + note.d);
osc.start(startTime);
osc.stop(startTime + note.d);
activeOscillators.push(osc);
startTime += note.d;
});
// Loop Ringtone sequence
loopTimeout = setTimeout(() => {
if (isPlaying) playRingtoneSequence();
}, (startTime - audioCtx.currentTime + 1.2) * 1000);
}
// Animate Visual Progress Bar
function updateProgress() {
currentTime += 0.05; // Updates per 50ms
if (currentTime > totalTime) {
currentTime = 0;
}
const percentage = (currentTime / totalTime) * 100;
fill.style.width = `${percentage}%`;
thumb.style.left = `${percentage}%`;
timeCurrentEl.textContent = formatTime(currentTime);
}
// Player Play/Pause Event Handler
btnPlay.addEventListener('click', () => {
isPlaying = !isPlaying;
if (isPlaying) {
btnPlay.textContent = '⏸';
btnPlay.style.paddingLeft = '0'; // Center pause icon
playRingtoneSequence();
progressInterval = setInterval(updateProgress, 50);
} else {
btnPlay.textContent = '▶';
btnPlay.style.paddingLeft = '4px'; // Offset play icon slightly to visual center
stopAudio();
clearInterval(progressInterval);
}
});
// Cleanup when component is removed from DOM to prevent ghost audio
const checkDOMTimer = setInterval(() => {
if (!document.body.contains(container)) {
stopAudio();
if (progressInterval) clearInterval(progressInterval);
clearInterval(checkDOMTimer);
}
}, 1000);
return container;
}
Apply Changes