I'm working on a SvelteKit project with Capacitor to build a mobile app. The app should display images and play audio files. Everything works fine in the browser, but when I run the app in the iOS simulator, the images and audio files do not load. I'm able to see the names of the audio track, go to the next track I have loaded and see the names of the images in the dropdown but no functionality from either.
Project Setup:
- SvelteKit: Using @sveltejs/adapter-static
- Capacitor: For building the mobile app
- Build Tool: Vite
svelte.config.js:
import adapter from '@sveltejs/adapter-static';import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';/** @type {import('@sveltejs/kit').Config} */const config = { preprocess: vitePreprocess(), kit: { adapter: adapter({ pages: 'build', assets: 'build', fallback: 'index.html' }), prerender: { entries: [] } }};export default config;
capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';const config: CapacitorConfig = { appId: 'com.example.app', appName: 'player2', webDir: 'build'};export default config;
src/routes/+layout.js:
export const prerender = 'auto';
Relevant code:
<script> import { onMount } from 'svelte'; import { writable } from 'svelte/store'; // Audio Context and Nodes let audioContext: AudioContext | null = $state(null); let gainNode: GainNode | null = $state(null); let audioSource: MediaElementAudioSourceNode | null = $state(null); let wavesurfer = $state<WaveSurfer | null>(null); // Audio Player State let audioPlayer = $state<HTMLAudioElement | null>(null); let isPlaying = $state(false); let currentPlaybackTime = $state('00:00'); let totalTrackDuration = $state('00:00'); let isMuted = $state(false); let volumeLevel = $state<number>(100); let savedVolumeLevel = $state<number>(100); let currentTrackPosition = $state(0); let seekPosition = $state(0); let seekValue = $state(0); let isDragging = $state(false); let divLoaded = $state(false); let currentTrack = $state<string>(''); let tracks: Object = $state({}); let songLoaded = $state(false); // Image State let sortedImages: string[] = $state([]); const imageModules = import.meta.glob('../images/*'); onMount(() => { wavesurfer = WaveSurfer.create({ container: '#waveform', waveColor: '#00bcd4', progressColor: '#d946ef', url: currentTrack, barWidth: 1, barGap: 0.5, barRadius: 1, height: 30 }); wavesurfer.setVolume(0); wavesurfer.on('click', (newTime) => { if (audioPlayer) { const duration = audioPlayer.duration; const newTimeInSeconds = newTime * duration; audioPlayer.currentTime = newTimeInSeconds; } }); sortedImages = Object.keys(imageModules).sort((a, b) => { const nameA = a.split('/images/')[1].toLowerCase(); const nameB = b.split('/images/')[1].toLowerCase(); const matchA = nameA.match(/^([a-z]+)-(\d+)/); const matchB = nameB.match(/^([a-z]+)-(\d+)/); if (matchA && matchB) { const prefixCompare = matchA[1].localeCompare(matchB[1]); if (prefixCompare !== 0) return prefixCompare; return parseInt(matchA[2]) - parseInt(matchB[2]); } return nameA.localeCompare(nameB); }); const randomIndex = Math.floor(Math.random() * sortedImages.length); selectedImage = `/src${sortedImages[randomIndex].slice(2)}`; tracks = import.meta.glob('/src/audio/*'); const trackKeys = Object.keys(tracks); if (trackKeys.length > 0) { currentTrack = trackKeys[0]; if (audioPlayer) { audioPlayer.src = currentTrack; audioPlayer.addEventListener('loadedmetadata', () => { if (audioPlayer) { totalTrackDuration = formatSecondsToMinutesSeconds(audioPlayer.duration); songLoaded = true; } }, { once: true } ); } } }); $effect(() => { seekValue = isDragging ? seekPosition : currentTrackPosition; if (audioPlayer) { setupAudioContext(); const handleTimeUpdate = () => { currentPlaybackTime = formatSecondsToMinutesSeconds(audioPlayer?.currentTime ?? null); currentTrackPosition = audioPlayer?.currentTime ?? 0; if (wavesurfer) { const progress = audioPlayer ? audioPlayer.currentTime / audioPlayer.duration : 0; wavesurfer.seekTo(progress); } }; const handleAudioEnded = () => { if (activeRepeat) { if (audioPlayer) { audioPlayer.currentTime = 0; audioPlayer.play(); } isPlaying = true; } else { playNextTrack(); } }; const handleAudioDuration = () => { totalTrackDuration = formatSecondsToMinutesSeconds(audioPlayer?.duration ?? null); document.documentElement.style.setProperty('--spin-duration', $tapeSpeeds.find((speed) => speed.isActive)?.speed +'s' ); }; handleAudioDuration(); audioPlayer.addEventListener('timeupdate', handleTimeUpdate); audioPlayer.addEventListener('ended', handleAudioEnded); audioPlayer.addEventListener('loadedmetadata', handleAudioDuration); return () => { audioPlayer?.removeEventListener('timeupdate', handleTimeUpdate); audioPlayer?.removeEventListener('ended', handleAudioEnded); audioPlayer?.removeEventListener('loadedmetadata', handleAudioDuration); }; } });</script><img src={selectedImage} alt="cd canvas" class={`max-h-48 max-w-full rounded-full object-contain ${isPlaying ? 'spin-slow' : 'spin-slow paused'}`}/><audio src={currentTrack} bind:this={audioPlayer} controls preload="auto" hidden></audio><select name="records" id="record" bind:value={selectedImage}> {#each sortedImages as image}<option value={`/src${image.slice(2)}`}> {image.split('../images/')[1]}</option> {/each}</select>
I'm not too familiar with Capacitor outside of the docs and a few reddit suggestions so anything and any help is appreciated!