Your music. Your artwork. Your own player.

Create your own Sound Sticker, test the music, then download a ready-to-host album player.

01. Upload your album art

Add front and back covers to customize your Sound Sticker.

No artwork selected yet.

Back cover preview

02. Upload your music

Select multiple MP3, WAV, M4A, or other browser-supported audio files. Edit track names, then drag the handle to reorder songs.

Drag the ⠿ handle to change track order. Songs are packaged into the downloaded ZIP. Very large WAV albums can take longer and may exceed your browser's memory.

03. Make your back cover playable

Choose a song, then tap its printed title on the back cover. Repeat for every track. Each orange marker becomes an invisible tap area in the published album.

Back cover mapping editor

Upload a back cover and music, then tap the cover to position each song.

Markers are only visible while editing. Published listeners tap the artwork itself. Track order changes keep each song's mapped spot.

04. Preview & publish

Test the album before sharing it with fans.

PHYSICAL RELEASES

CD PRESSING

Custom CD pressing option — coming next.

CD PRESSING
`} async function makeCoverIcon(file,size){ const bitmap=await createImageBitmap(file), canvas=document.createElement('canvas'); canvas.width=canvas.height=size; const ctx=canvas.getContext('2d'); const side=Math.min(bitmap.width,bitmap.height); ctx.drawImage(bitmap,(bitmap.width-side)/2,(bitmap.height-side)/2,side,side,0,0,size,size); bitmap.close?.(); return await new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Icon conversion failed')),'image/png')); } // ZIP store writer: creates a standards-compliant ZIP without external libraries. const encoder=new TextEncoder();function crc32(buf){let c=0xffffffff;for(const b of buf){c^=b;for(let k=0;k<8;k++)c=(c>>>1)^((c&1)?0xedb88320:0)}return(c^0xffffffff)>>>0}function u16(n){return[n&255,(n>>>8)&255]}function u32(n){return[n&255,(n>>>8)&255,(n>>>16)&255,(n>>>24)&255]}async function zipFiles(files){let parts=[],central=[],offset=0;for(const [name,blob] of files){const filename=encoder.encode(name),bytes=new Uint8Array(await blob.arrayBuffer()),crc=crc32(bytes),len=bytes.length;const local=new Uint8Array([...u32(0x04034b50),...u16(20),...u16(0x800),...u16(0),...u16(0),...u16(0),...u32(crc),...u32(len),...u32(len),...u16(filename.length),...u16(0),...filename]);parts.push(local,bytes);central.push(new Uint8Array([...u32(0x02014b50),...u16(20),...u16(20),...u16(0x800),...u16(0),...u16(0),...u16(0),...u32(crc),...u32(len),...u32(len),...u16(filename.length),...u16(0),...u16(0),...u16(0),...u16(0),...u32(0),...u32(offset),...filename]));offset+=local.length+len}const csize=central.reduce((a,b)=>a+b.length,0),end=new Uint8Array([...u32(0x06054b50),...u16(0),...u16(0),...u16(files.length),...u16(files.length),...u32(csize),...u32(offset),...u16(0)]);return new Blob([...parts,...central,end],{type:'application/zip'})} $('preview').onclick=()=>{const d=data();if(front)d.front=URL.createObjectURL(front);if(back)d.back=URL.createObjectURL(back);d.songs=songs.map(s=>({name:s.name,path:URL.createObjectURL(s.file)}));const blob=new Blob([playerHtml(d)],{type:'text/html'}),url=URL.createObjectURL(blob);const w=window.open(url,'_blank');if(!w)$('status').textContent='Preview pop-up blocked. Allow pop-ups, or use the preview player on this page.'}; function downloadBlob(blob,name){const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),60000)} $('export').onclick=async()=>{const st=$('status'),btn=$('export');if(!songs.length||!front){st.textContent='Upload your front cover and at least one song first.';return}btn.disabled=true;st.textContent='Creating music and artwork ZIP…';try{const d=data(),files=[['Front_Cover'+ext(front.name),front]];if(back)files.push(['Back_Cover'+ext(back.name),back]);songs.forEach((song,i)=>files.push([String(i+1).padStart(2,'0')+' - '+slug(song.name)+ext(song.file.name),song.file]));downloadBlob(await zipFiles(files),slug(d.artist)+'_'+slug(d.album)+'_MUSIC.zip');st.textContent='Downloaded: songs and album artwork only.'}catch(e){st.textContent='Download failed: '+e.message}finally{btn.disabled=false}}; $('publishzip').onclick=async()=>{const st=$('status'),btn=$('publishzip');if(!songs.length||!front){st.textContent='Upload your front cover and at least one song first.';return}btn.disabled=true;st.textContent='Preparing installable glass player and album cover icons…';try{const d=data(),icons=[['icons/icon-192.png',await makeCoverIcon(front,192)],['icons/icon-512.png',await makeCoverIcon(front,512)],['icons/apple-touch-icon.png',await makeCoverIcon(front,180)]],manifest={name:d.album+' — '+d.artist,short_name:d.album.slice(0,24),start_url:'./index.html',scope:'./',display:'standalone',background_color:'#090909',theme_color:'#090909',icons:[{src:'icons/icon-192.png',sizes:'192x192',type:'image/png',purpose:'any'},{src:'icons/icon-512.png',sizes:'512x512',type:'image/png',purpose:'any'}]},files=[['index.html',new Blob([playerHtml(d)],{type:'text/html'})],['manifest.webmanifest',new Blob([JSON.stringify(manifest,null,2)],{type:'application/manifest+json'})],['README.txt',new Blob(['SOUND STICKER ALBUM PLAYER — HTTPS DEPLOYMENT\n\nExtract this ZIP and upload its CONTENTS to a dedicated album URL on Cloudflare Pages or another HTTPS host.\nOpen the album URL in iPhone Safari > Share > Add to Home Screen.\nThe album cover is used as the icon.\nThe music-only ZIP is a separate download.\nThis builder cannot automatically publish albums to the server.\n'],{type:'text/plain'})],...icons,[d.front,front]];if(back)files.push([d.back,back]);songs.forEach((song,i)=>files.push([d.songs[i].path,song.file]));downloadBlob(await zipFiles(files),slug(d.artist)+'_'+slug(d.album)+'_PLAYER_DEPLOY.zip');st.textContent='Player ZIP ready. Deploy its extracted contents to an HTTPS album URL, then Add to Home Screen from Safari.'}catch(e){st.textContent='Player export failed: '+e.message}finally{btn.disabled=false}}; $('publishlive').onclick=async()=>{ const btn=$('publishlive'),status=$('publishresult'),token=$('pubtoken').value.trim(); if(!front||!songs.length){status.textContent='Upload front cover and at least one track.';return} if(!token){status.textContent='Enter your private publisher key.';return} btn.disabled=true;status.textContent='Preparing album…'; try{ const d=data(), slugId=slug(d.artist)+'-'+slug(d.album)+'-'+Date.now().toString(36); const icons=[['icons/icon-192.png',await makeCoverIcon(front,192)],['icons/icon-512.png',await makeCoverIcon(front,512)],['icons/apple-touch-icon.png',await makeCoverIcon(front,180)]]; const manifest={name:d.album+' — '+d.artist,short_name:d.album.slice(0,24),start_url:'./',scope:'./',display:'standalone',background_color:'#090909',theme_color:'#090909',icons:[{src:'icons/icon-192.png',sizes:'192x192',type:'image/png'},{src:'icons/icon-512.png',sizes:'512x512',type:'image/png'}]}; const files=[['index.html',new Blob([playerHtml(d)],{type:'text/html'})],['manifest.webmanifest',new Blob([JSON.stringify(manifest)],{type:'application/manifest+json'})],...icons,[d.front,front]]; if(back)files.push([d.back,back]);songs.forEach((song,i)=>files.push([d.songs[i].path,song.file])); // Authenticate before uploading large files; never send secret as part of the public album URL. const auth=await fetch('https://sound-sticker-publisher.989zae.workers.dev/api/soundsticker/publish',{method:'POST',headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'},body:JSON.stringify({id:slugId,files:files.map(([name,b])=>({name,size:b.size,type:b.type}))})}); if(!auth.ok)throw Error('Publishing authorization failed ('+auth.status+'): '+(await auth.text()).slice(0,140)); for(let i=0;i