GeoTunes is a Kotlin Multiplatform app that lets you discover live radio stations from around the world using an interactive 2D map. It lets you stream radio stations and play songs, identify them in real time, and shows what the song is about using AI. You can also build your personal library by saving the songs and ask AI about your saved songs, music taste, or get recommendations as you want.
Built with Compose Multiplatform for Android and iOS, GeoTunes demonstrates how modern Kotlin can used to build such a cross-platform application.
Demo.mp4
I am an undergraduate Computer Science student and I have worked with technologies like Python, JavaScript, Kotlin, C++ in past few years. I genuinely enjoy building things from scratch, taking up challenging ideas and turning them into working projects has helped me develop strong problem solving skills. Compared to backend and general programming, I have had relatively less experience with building native mobile apps. I used to make mobile apps with drag-and-drop tools like Kodular and MIT App Inventor when I was in school.
I listen to a wide variety of music across different genres, languages, and cultures. I wanted to make an app that would let me discover music from different parts of the world, identify those songs in real time, and help me understand their cultural or emotional context.
So I set out to create GeoTunes — a Kotlin Multiplatform app built using Compose Multiplatform for Android and iOS where I used open-source Radio Browser API to get the radio stations data and Shazam to detect the songs playing on the radio. Google Gemini is used to generate summaries of the song and it also powers the AI chat. My experience in Node.js/Express helped me make a REST API server to process the radio stations data and use in the app. This project reflects my interest in music, culture, and modern cross-platform app development. It helped me learn new and advanced concepts of Kotlin Multiplatform along the way.
-
Discover Screen
- Interactive 2D Map
- Dice roll button for random station
- Play/Pause audio stream
- Station name, city & country info
- Detect songs playing live
- Save to library
- Get song summary from AI
-
Ask AI Screen
- Chat with AI
- Last 10 messages used for context preservation
- Clear conversation history and context with delete button
-
Library Screen
- Saved songs shown here
- Remove songs from library using delete button
- macOS for iOS device builds
- IntelliJ IDEA or Android Studio
- XCode
- JDK
- Kotlin Multiplatform Plugin
- Node.js
- Apple Developer Account
- Maptiler Account
- Google Gemini
- Android
- iOS
-
Clone GeoTunes repository:
git clone https://github.com/realBoltDev/GeoTunes.git -
Create a
local.propertiesfile and add the following contents:
MAPTILER_URI=your-maptiler-uri
MAPTILER_API_KEY=your-maptiler-api-key
GEOTUNES_API_BASE_URL=your-rest-api-server-base-url
GEMINI_API_KEY=your-gemini-api-key
GEMINI_MODEL=gemini-2.5-flash
JWT_SECRET_KEY=your-jwt-secret-key
- Steps to get values for each of those:
-
MAPTILER_URI & MAPTILER_API_KEY
- Create a Maptiler account.
- Go to Upload Map page and upload this map style.
- Choose rendering format as WebP and create map.
- Copy the link to vector style JSON and assign to MAPTILER_URI. Remove the key from URI and assign it to MAPTILER_API_KEY.
-
GEOTUNES_API_BASE_URL
- Add
localhost:3000since that's where Node.js server will be running locally.
- Add
-
GEMINI_API_KEY & GEMINI_MODEL
- Create a Gemini API Key from Google AI Studio.
- Select a model from here.
-
JWT_SECRET_KEY
- Generate a random alphanumeric key. This will be used as Bearer token to send request from app and authenticate to
/shazam-jwtAPI endpoint.
- Generate a random alphanumeric key. This will be used as Bearer token to send request from app and authenticate to
Note: An Apple Account with valid Apple Developer Program subscription is required to use ShazamKit.
- iOS Device
- Follow this guide to enable ShazamKit service on your iOS app.
- Android Device
- Download ShazamKit SDK for Android and unzip it.
- Move the
shazamkit-android-release.aarfile toGeoTunes/composeApp/libsdirectory. - Go to Identifiers tab in In Certificates, Identifiers & Profiles.
- Click add button and select Media IDs.
- Enter your app Identifier, Description and enable ShazamKit. Click Continue -> Register.
- Now go to Keys tab in Certificates, Identifiers & Profiles
- Click add button. Add key name and enable Media Services (MusicKit, ShazamKit, Apple Music Feed). Then click configure for it.
- Select your Media ID and click Save -> Continue -> Register.
- Download the key file and move it to
GeoTunes/serverdirectory. Copy the Key ID. - Copy your Team ID from Account visible in Membership details.
- Create a
.envfile inGeoTunes/serverdirectory and the following contents:TEAM_ID="your-team-id" KEY_ID="your-key-id" PRIVATE_KEY_FILE="your-private-key-file-name" JWT_SECRET_KEY="your-jwt-secret-key"- Add the Team ID and Key ID copied from before
- Add your downloaded private key file name and the JWT secret key generated from before
| API Server | Android | iOS |
|---|---|---|
Go to GeoTunes/server and run npm install, then run node index.js in GeoTunes/server/src |
Open the project in Intellij Idea and run the composeApp configuration |
Open iosApp/iosApp.xcodeproj in XCode |
API server will start on port 3000 |
Select an emulator or device and click Run | Select a device or simulator and run the app |
To start streaming a station, zoom in and click on a green dot which resembles radio station by interacting in the 2D Map. You can move around or zoom in/out freely. If you want to stream a random radio station, click the roll dice button on the bottom right of the map.
To play or pause the stream, you can click on the toggle button in the Radio Station section.
In order to detect song, first play a radio station. Then click on Detect button which will ask you permission to access your microphone. This is required because Shazam records audio to detect songs playing around you. When song changes on the radio or you move to a different station, use the Detect button again.
After song is detected by Shazam, it will show its title, artist, and artwork along with a Save button. Use it to save the song to your library.
Song summary is automatically generated via Gemini when song is detected.
Go to Library screen from the Navbar to see your saved songs. Use the delete button on the top right of the cards to delete individual songs.
In the Ask AI screen you can chat with AI. You can ask information about your saved songs, get suggestions, etc. Use the delete button on the top right to clear the conversation history and reset context.
| Library | Purpose |
|---|---|
| Kotlin Multiplatform | Shared business logic across Android, iOS, and Desktop |
| Compose Multiplatform | Declarative UI framework for Android, Desktop, and shared UI |
| Kotlinx Serialization | JSON serialization/deserialization for network and data models |
| Ktor Client | Multiplatform HTTP client used for API communication |
| Koin | Dependency Injection for shared and platform-specific code |
| Kotlinx Coroutines | Asynchronous programming and concurrency |
| Generative AI | AI features using Gemini models via a KMP-compatible client |
| MapLibre Compose | Interactive map rendering for geographic features |
| Navigation Compose | Navigation handling in Compose UI |
| Coil | Image loading optimized for Compose Multiplatform |
| Media3 ExoPlayer | Audio streaming and playback on Android |
| Multiplatform Markdown Renderer | Rendering AI responses and content in Markdown |
| BuildKonfig | Compile-time configuration (API keys, environment variables) |
| spmForKmp | Swift Package Manager integration for iOS native libraries |
| Library | Purpose |
|---|---|
| Express | HTTP server and REST API framework |
| Axios | HTTP client for external API requests |
| better-sqlite3 | Embedded SQLite database for fast local storage |
| jsonwebtoken | JWT-based authentication and token handling |
| rate-limiter-flexible | API rate limiting and abuse protection |
| compression | Gzip/Brotli compression for API responses |
| cors | Cross-Origin Resource Sharing (CORS) support |
| dotenv | Environment variable management |
| Turf.js | Geospatial utilities (point-in-polygon checks) |