I got tired of having to tell people manually what song I was listening to, so I built something that automatically shares my current Apple Music track on my website. It’s nothing fancy, but it solved my problem.
The problem I wanted to solve
I spend a lot of time listening to music while coding and thought it would be great to show what’s playing on my website in real time. I had seen similar features on other developers’ sites and wanted something similar for my own.
The challenge was that Apple Music doesn’t have a public API for this kind of thing. I needed to extract the current track information from the app and send it to my website without any official integration.
Here’s the overall architecture I ended up with:

Getting Data Out of Apple Music
The first hurdle was actually obtaining the track information. After some research, I discovered that AppleScript could interact with the Music app. Setting up the authentication was frustrating, but once I had the correct permissions, it was straightforward.
Here’s the AppleScript that retrieves the current track:
tell application "Music"
if it is running then
if player state is playing then
set pState to player state
set pPosition to player position
set cTrack to current track
set trackInfo to "{\"status\": \"playing\", \"persistent ID\": \"" & persistent ID of cTrack & "\", \"name\": \"" & name of cTrack & "\", \"time\": \"" & time of cTrack & "\", \"duration\": \"" & duration of cTrack & "\", \"artist\": \"" & artist of cTrack & "\", \"album\": \"" & album of cTrack & "\" }"
return trackInfo
else
return "{\"status\": \"not playing\"}"
end if
else
return "{\"status\": \"not running\"}"
end if
end tell
The script returns JSON directly, as dealing with AppleScript data structures in Python was too much hassle. This means I can simply parse it as JSON in Python.
$ osascript export.applescript
{"status": "not playing"}
$ osascript export.applescript
{"status": "playing", "persistent ID": "1A3BB81D89DE1B39", "name": "SAUDADE", "time": "2:21", "duration": "141,429000854492", "artist": "Dexa. & 6minaprès", "album artist": "Dexa.", "composer": "Gael six minutes, 6astardcanrise canr rise & Dexa Dexa Dexa", "album": "Station d'Atlas - EP", "genre": "Pop", "played count": "5", "pState": "playing", "pPosition": "1,957999944687" }
Building the Bridge
The Python script runs continuously, calling the AppleScript every few seconds. When it detects a new song using the persistent ID, it fetches additional metadata from the iTunes API and posts all the information to my Flask backend.
def get_current_song():
try:
output = subprocess.check_output(['osascript', 'export.applescript']).decode('utf-8').strip()
return output
except subprocess.CalledProcessError as e:
printerr(e)
return e
def main():
persistendId = ''
prevstatus = ''
while True:
currentsong = json.loads(get_current_song())
if currentsong['status'] == 'playing':
if currentsong['persistent ID'] != persistendId:
persistendId = currentsong['persistent ID']
currentsong['timestamp'] = time.time()
# Fetch artwork and iTunes URLs from iTunes API
(currentsong['artwork_url'], currentsong['itunes_url'], currentsong['artist_url']) = get_track_extras(currentsong['name'], currentsong['artist'], currentsong['album'])
# Extract dominant color from artwork for UI theming
currentsong['dominantcolor'] = get_dominant_color_from_url(currentsong['artwork_url'])
printout(f"{post(currentsong)}")
The script logs everything so I can see what’s happening:
14:23:45 : {"message": "Content set successfully."}
14:27:12 : {"message": "Content set successfully."}
14:30:38 : {"message": "Content set successfully."}
One interesting challenge was the timing. To avoid spamming the API, the script calculates how long it will take for the current song to end and then sleeps for that duration plus a few seconds’ buffer time.
The API Backend
The Flask backend is extremely simple: it has just two endpoints and an in-memory cache.
from flask import Flask, request, jsonify
import json
import hashlib
app = Flask(__name__)
cache = {}
@app.route('/music/set', methods=['POST'])
def set_content():
data = request.get_json()
user = data.get('user')
password = data.get('password')
if user in users and users[user] == hashlib.sha256(password.encode()).hexdigest():
cache.update(data)
cache.pop('user', None) # Don't store credentials
cache.pop('password', None)
return jsonify({'message': f'Content set successfully.'})
else:
return jsonify({'message': 'Invalid user or password.'}), 401
@app.route('/music/get', methods=['GET'])
def display_content():
return jsonify(cache)
I added basic authentication because I didn’t want random people to be able to update my ‘Currently Playing’ status. The cache simply stores the latest track information returned to anyone who accesses the GET endpoint.
Running It as a Service
Getting this to run automatically when booting macOS was another minor issue. In the end, I used launchd with a plist file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.music-exp</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/python3</string>
<string>/Users/noham/Documents/export.py</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/noham/Documents</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
What I Ended Up With
The system works pretty well. When I play a song on Apple Music, it appears on my website within a few seconds, complete with artwork, artist information and a dominant colour extracted from the album art for theming.

Here’s what the API returns:
{
"album": "Station d'Atlas - EP",
"album artist": "Dexa.",
"artist": "Dexa.",
"artist_url": "https://music.apple.com/us/artist/dexa/1534658078?uo=4",
"artwork_url": "https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/9f/ca/fc/9fcafcb7-a3a1-5c4d-31f0-86dbfcc016ec/artwork.jpg/200x200bb.jpg",
"composer": "Dexa Dexa Dexa & Yanis Yvvid Prod",
"dominantcolor": [132, 69, 13],
"duration": "139,285995483398",
"genre": "Pop",
"itunes_url": "https://music.apple.com/us/album/sadida/1820035593?i=1820035597&uo=4",
"name": "SADIDA",
"pPosition": "81,157997131348",
"pState": "playing",
"persistent ID": "E942F8A52AEAEA1E",
"played count": "5",
"status": "playing",
"time": "2:19",
"timestamp": 1751116587.6413
}
The front-end JavaScript uses the timestamp to calculate real-time progress, meaning the progress bar moves even if the API isn’t updated every second.
If You Want to Try It
The code is on GitHub if you’re interested. Fair warning - it’s pretty specific to my setup and macOS. You’ll need to:
- Set up the Flask backend.
- Configure the Python script with your credentials.
- Manage macOS permissions for AppleScript.
- Set up the launchd service.
This solution is not the most elegant, but it works for my needs. The main limitation is that it only works when Apple Music is running on my Mac. Therefore, if I’m listening on my phone or elsewhere, the website shows nothing.