You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
audioUrl = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
lyricsText = "Incoming call...\nRing, ring, ring!\nCan you hear the music play?\nThis is your custom android ringtone.\nPick up the phone to join the fun!\nLet the melody guide you through the day.\nDon't let it go to voicemail!\nWe are jamming now,\nMusic to your ears.",
title = "Incoming Call",
artist = "Android User"
) {
// 1. Create the top-level container element
const wrapper = document.createElement('div');
wrapper.style.width = "100%";
// 2. Safely grab the background image URL from originalImg
let imgSrc = '';
if (originalImg) {
// If it's a tainted canvas (or general canvas), we try to extract; otherwise use src
if (originalImg instanceof HTMLCanvasElement) {
try { imgSrc = originalImg.toDataURL(); } catch (e) { imgSrc = ''; }
} else {
imgSrc = originalImg.src || '';
}
}
// 3. Injecting Custom Font & Styles
const styleEl = document.createElement('style');
styleEl.innerHTML = `
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
.ringtone-player-wrapper {
container-type: inline-size;
position: relative;
width: 100%;
aspect-ratio: 2.55 / 1;
background: #111;
overflow: hidden;
border-radius: min(16px, 2cqi);
font-family: 'Roboto', sans-serif;
box-shadow: 0 10px 30px rgba(0,0,0,0.8);
box-sizing: border-box;
user-select: none;
}
.rp-bg-blur {
position: absolute;
top: -10%; left: -10%;
width: 120%; height: 120%;
background-image: url("${imgSrc}");
background-size: cover;
background-position: center;
filter: blur(15px) brightness(35%);
z-index: 1;
}
.rp-overlay-content {
position: absolute;
inset: 0;
z-index: 2;
display: flex;
align-items: center;
padding: 4%;
background: linear-gradient(90deg, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.1) 100%);
box-sizing: border-box;
}
.rp-album-art {
height: clamp(50px, 90%, 800px);
aspect-ratio: 1;
border-radius: 50%;
object-fit: cover;
border: clamp(2px, 0.5cqi, 6px) solid #fff;
box-shadow: 0 8px 25px rgba(0,0,0,0.6);
animation: rp-spin 15s linear infinite;
animation-play-state: paused;
flex-shrink: 0;
}
@keyframes rp-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.rp-info {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 4%;
overflow: hidden;
box-sizing: border-box;
}
.rp-title {
color: #fff;
font-size: clamp(14px, 4cqi, 40px);
font-weight: 700;
margin: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
letter-spacing: 0.5px;
}
.rp-artist {
color: #bbb;
font-size: clamp(10px, 2.5cqi, 26px);
margin: 1% 0 0 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.rp-lyrics-viewport {
flex: 1;
width: 100%;
margin: 2% 0;
position: relative;
overflow: hidden;
/* Fade top and bottom for slick display */
mask-image: linear-gradient(180deg, transparent 0%, black 20%, black 80%, transparent 100%);
-webkit-mask-image: linear-gradient(180deg, transparent 0%, black 20%, black 80%, transparent 100%);
}
.rp-lyrics-inner {
position: absolute;
top: 0; left: 0; right: 0;
display: flex;
flex-direction: column;
transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.rp-lyrics-line {
color: #ffffff66;
font-size: clamp(10px, 2.3cqi, 22px);
margin: 1% 0;
transition: all 0.3s ease;
transform-origin: left center;
}
.rp-lyrics-line.active {
color: #1bd760;
font-size: clamp(12px, 2.8cqi, 26px);
font-weight: bold;
opacity: 1;
}
.rp-controls {
display: flex;
flex-direction: column;
gap: max(1%, 4px);
flex-shrink: 0;
}
.rp-progress-row {
display: flex;
align-items: center;
gap: 2%;
}
.rp-time {
color: #ccc;
font-size: clamp(8px, 1.8cqi, 18px);
font-variant-numeric: tabular-nums;
min-width: 8%;
}
.rp-slider {
flex: 1;
-webkit-appearance: none;
background: transparent;
height: clamp(4px, 1cqi, 12px);
}
.rp-slider:focus { outline: none; }
.rp-slider::-webkit-slider-runnable-track {
width: 100%;
height: clamp(3px, 0.8cqi, 8px);
cursor: pointer;
background: #ffffff44;
border-radius: 4px;
}
.rp-slider::-webkit-slider-thumb {
-webkit-appearance: none;
height: clamp(10px, 2.5cqi, 20px);
width: clamp(10px, 2.5cqi, 20px);
border-radius: 50%;
background: #1bd760;
cursor: pointer;
transform: translateY(-33%);
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
}
.rp-buttons-row {
display: flex;
align-items: center;
justify-content: flex-start;
gap: clamp(10px, 3cqi, 30px);
margin-top: 2%;
}
.rp-btn {
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 50%;
width: clamp(30px, 7cqi, 60px);
height: clamp(30px, 7cqi, 60px);
cursor: pointer;
color: #fff;
transition: all 0.2s ease;
box-shadow: 0 4px 10px rgba(0,0,0,0.5);
}
.rp-btn:hover {
transform: scale(1.1);
filter: brightness(1.1);
}
.rp-btn .material-icons {
font-size: clamp(16px, 3.5cqi, 32px);
}
.rp-btn-accept { background: #4caf50; }
.rp-btn-action { background: #2196F3; }
.rp-btn-reject { background: #f44336; }
`;
wrapper.appendChild(styleEl);
// 4. Create the HTML Structure
const playerWrapper = document.createElement('div');
playerWrapper.className = 'ringtone-player-wrapper';
// Background Layer
const bgBlur = document.createElement('div');
bgBlur.className = 'rp-bg-blur';
playerWrapper.appendChild(bgBlur);
// Foreground Overlay
const overlay = document.createElement('div');
overlay.className = 'rp-overlay-content';
// Album Art
const albumArt = document.createElement('img');
albumArt.className = 'rp-album-art';
albumArt.src = imgSrc || 'data:image/svg+xml;utf8,<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><rect width="200" height="200" fill="%23333"/></svg>';
overlay.appendChild(albumArt);
// Info Container
const infoContainer = document.createElement('div');
infoContainer.className = 'rp-info';
// Title & Artist
const headerDiv = document.createElement('div');
const titleEl = document.createElement('h2');
titleEl.className = 'rp-title';
titleEl.innerText = title;
const artistEl = document.createElement('h3');
artistEl.className = 'rp-artist';
artistEl.innerText = artist;
headerDiv.appendChild(titleEl);
headerDiv.appendChild(artistEl);
infoContainer.appendChild(headerDiv);
// Lyrics Viewport
const lyricsViewport = document.createElement('div');
lyricsViewport.className = 'rp-lyrics-viewport';
const lyricsInner = document.createElement('div');
lyricsInner.className = 'rp-lyrics-inner';
// Parse lyrics and append elements
const lines = lyricsText.split('\n').map(l => l.trim()).filter(l => l !== '');
lines.forEach(text => {
const p = document.createElement('div');
p.className = 'rp-lyrics-line';
p.innerText = text;
lyricsInner.appendChild(p);
});
lyricsViewport.appendChild(lyricsInner);
infoContainer.appendChild(lyricsViewport);
// Audio Elements & Controls
const controlsDiv = document.createElement('div');
controlsDiv.className = 'rp-controls';
const progRow = document.createElement('div');
progRow.className = 'rp-progress-row';
const timeDisplay = document.createElement('div');
timeDisplay.className = 'rp-time';
timeDisplay.innerText = "0:00 / 0:00";
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '100';
slider.value = '0';
slider.className = 'rp-slider';
progRow.appendChild(timeDisplay);
progRow.appendChild(slider);
const btnsRow = document.createElement('div');
btnsRow.className = 'rp-buttons-row';
const playBtn = document.createElement('button');
playBtn.className = 'rp-btn rp-btn-accept';
playBtn.innerHTML = '<span class="material-icons">call</span>';
const rejectBtn = document.createElement('button');
rejectBtn.className = 'rp-btn rp-btn-reject';
rejectBtn.innerHTML = '<span class="material-icons">call_end</span>';
btnsRow.appendChild(playBtn);
btnsRow.appendChild(rejectBtn);
controlsDiv.appendChild(progRow);
controlsDiv.appendChild(btnsRow);
infoContainer.appendChild(controlsDiv);
overlay.appendChild(infoContainer);
playerWrapper.appendChild(overlay);
// Hidden Audio Element
const audio = document.createElement('audio');
audio.crossOrigin = "anonymous";
audio.src = audioUrl;
playerWrapper.appendChild(audio);
wrapper.appendChild(playerWrapper);
// 5. JavaScript Interactivity Logic
const formatTime = (secs) => {
if (isNaN(secs)) return "0:00";
const m = Math.floor(secs / 60);
const s = Math.floor(secs % 60);
return `${m}:${s.toString().padStart(2, '0')}`;
};
const lyricElements = Array.from(lyricsInner.children);
let lastActiveIndex = -1;
// Handles play/pause toggle natively
playBtn.onclick = () => {
if (audio.paused) {
audio.play().catch(console.error);
} else {
audio.pause();
}
};
rejectBtn.onclick = () => {
audio.pause();
audio.currentTime = 0;
titleEl.innerText = "Call Ended";
setTimeout(() => { titleEl.innerText = title; }, 3000);
};
audio.onplay = () => {
albumArt.style.animationPlayState = 'running';
playBtn.className = 'rp-btn rp-btn-action';
playBtn.innerHTML = '<span class="material-icons">pause</span>';
titleEl.innerText = `♫ ${title}`;
};
audio.onpause = () => {
albumArt.style.animationPlayState = 'paused';
playBtn.className = 'rp-btn rp-btn-action';
playBtn.style.background = '#FF9800'; // Change to orange to show paused state
playBtn.innerHTML = '<span class="material-icons">play_arrow</span>';
titleEl.innerText = `${title} (Paused)`;
if (audio.currentTime === 0) {
playBtn.className = 'rp-btn rp-btn-accept';
playBtn.innerHTML = '<span class="material-icons">call</span>';
playBtn.style.background = '';
titleEl.innerText = title;
}
};
audio.ontimeupdate = () => {
const duration = audio.duration || 1;
const current = audio.currentTime;
// Update slider and time display
slider.value = (current / duration) * 100;
timeDisplay.innerText = `${formatTime(current)} / ${formatTime(duration)}`;
// Sync Lyrics based on elapsed percentage (Simulation)
const totalLines = lyricElements.length;
if (totalLines === 0) return;
const progressStr = current / duration;
let activeIndex = Math.floor(progressStr * totalLines);
activeIndex = Math.min(activeIndex, totalLines - 1); // Clamp
if (activeIndex !== lastActiveIndex) {
if(lyricElements[lastActiveIndex]) {
lyricElements[lastActiveIndex].classList.remove('active');
}
const activeLine = lyricElements[activeIndex];
activeLine.classList.add('active');
lastActiveIndex = activeIndex;
// Auto-scroll logic: Center the active line inside viewport
const viewportHalfHeight = lyricsViewport.clientHeight / 2;
const lineCenterOffset = activeLine.offsetTop + (activeLine.clientHeight / 2);
let transformY = viewportHalfHeight - lineCenterOffset;
// Apply bounds so we don't scroll too far out visually
if (transformY > 0) transformY = 0;
lyricsInner.style.transform = `translateY(${transformY}px)`;
}
};
slider.oninput = (e) => {
const percent = parseFloat(e.target.value);
if(!isNaN(audio.duration)){
audio.currentTime = (percent / 100) * audio.duration;
}
};
return wrapper;
}
Apply Changes