Please bookmark this page to avoid losing your image tool!

Android Ringtone MP3 Music And Lyric Photo Player

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool allows users to generate a visually engaging, interactive music player interface styled like an Android incoming call screen. By providing an image, an audio URL, song details (title and artist), and lyrics, the tool creates a dynamic component featuring a rotating album art display, a blurred background based on the uploaded image, a synchronized lyrics viewport that scrolls with the music, and functional playback controls. It is ideal for developers or creators looking to simulate a mobile ringtone experience or design custom music player UI elements for web projects.

Leave a Reply

Your email address will not be published. Required fields are marked *