Skip to content

Basic Music Player

A working desktop music player is one of the best mid-beginner projects because it combines four things at once: a graphical user interface (windows, buttons, lists), file-system browsing (loading a folder), audio playback (actually producing sound), and event-driven programming (the program reacts to button clicks instead of running top-to-bottom). The result feels like a real application from the moment it launches.

In this tutorial you will build a music player that:

  • Opens a window with playback controls (play, pause, stop, resume).
  • Lets the user pick a folder, then lists every audio file inside it.
  • Plays the selected song through your speakers.
  • Shows which song is currently playing and the player’s status.

By the end you will be comfortable with Tkinter widgets, pygame.mixer, the os module for folder listings, and the event loop model that all GUI frameworks share.

  • Python 3.6 or above.
  • A text editor or IDE.
  • A few .mp3 (or .wav) files in a folder somewhere on your disk.
  • Comfort running a Python script from the terminal.
  • Familiarity with functions and conditionals (see Simple Calculator if you need a refresher).
LibraryWhy we use it
TkinterStandard-library GUI framework. Provides windows, buttons, labels, listboxes.
tkinter.filedialogA pre-made directory-picker dialog.
pygame.mixerA sound system that loads and plays audio files. We use Pygame only for the mixer, not for graphics.
osLists files in a folder, builds file paths in a cross-platform way.

Tkinter ships with Python. Pygame does not — install it with pip:

install
pip install pygame

Up to this point your programs ran top-to-bottom: read input, compute, print, exit. A GUI program is different. It enters a loop managed by Tkinter (root.mainloop()) that does three things forever:

  1. Wait for an event (mouse click, keypress, window resize).
  2. Run the function you registered for that event.
  3. Repaint the window.

You never write the loop yourself. You just register callbacks — functions that Tkinter will call when something happens. Button(command=play_song) is registering play_song as the callback for “this button was clicked.”

  1. Create a folder named basic-music-player.
  2. Inside it, create basicmusicplayer.py.
  3. Have a separate folder ready with a handful of music files.
Basic Music Player pch.viewSource
Basic Music Player
# Basic Music Player
# Credit: https://pythongeeks.org/python-music-player/

# Import Modules
from tkinter import *              # pip install tkinter
from tkinter import filedialog
import pygame.mixer as mixer        # pip install pygame
import os

# Initializing the mixer
mixer.init()

# Creating the master GUI for python music player
root = Tk()
root.geometry('700x220')
root.title('Basic Music Player')
root.resizable(0, 0)

# Play, Stop, Load and Pause & Resume functions
def play_song(song_name: StringVar, songs_list: Listbox, status: StringVar):
    song_name.set(songs_list.get(ACTIVE))
    mixer.music.load(songs_list.get(ACTIVE))
    mixer.music.play()
    status.set("Song PLAYING")
def stop_song(status: StringVar):
    mixer.music.stop()
    status.set("Song STOPPED")
def load(listbox):
    os.chdir(filedialog.askdirectory(title='Open a songs directory'))
    tracks = os.listdir()
    for track in tracks:
        listbox.insert(END, track)
def pause_song(status: StringVar):
    mixer.music.pause()
    status.set("Song PAUSED")
def resume_song(status: StringVar):
    mixer.music.unpause()
    status.set("Song RESUMED")
    
# All the frames
song_frame = LabelFrame(root, text='Current Song', bg='LightBlue', width=400, height=80)
song_frame.place(x=0, y=0)
button_frame = LabelFrame(root, text='Control Buttons', bg='Turquoise', width=400, height=120)
button_frame.place(y=80)
listbox_frame = LabelFrame(root, text='Playlist', bg='RoyalBlue')
listbox_frame.place(x=400, y=0, height=200, width=300)
# All StringVar variables
current_song = StringVar(root, value='<Not selected>')
song_status = StringVar(root, value='<Not Available>')

# Playlist ListBox
playlist = Listbox(listbox_frame, font=('Helvetica', 11), selectbackground='Gold')

scroll_bar = Scrollbar(listbox_frame, orient=VERTICAL)
scroll_bar.pack(side=RIGHT, fill=BOTH)

playlist.config(yscrollcommand=scroll_bar.set)

scroll_bar.config(command=playlist.yview)

playlist.pack(fill=BOTH, padx=5, pady=5)

# SongFrame Labels
Label(song_frame, text='CURRENTLY PLAYING:', bg='LightBlue', font=('Times', 10, 'bold')).place(x=5, y=20)

song_lbl = Label(song_frame, textvariable=current_song, bg='Goldenrod', font=("Times", 12), width=25)
song_lbl.place(x=150, y=20)

# Buttons in the main screen
pause_btn = Button(button_frame, text='Pause', bg='Aqua', font=("Georgia", 13), width=7,
                    command=lambda: pause_song(song_status))
pause_btn.place(x=15, y=10)

stop_btn = Button(button_frame, text='Stop', bg='Aqua', font=("Georgia", 13), width=7,
                  command=lambda: stop_song(song_status))
stop_btn.place(x=105, y=10)

play_btn = Button(button_frame, text='Play', bg='Aqua', font=("Georgia", 13), width=7,
                  command=lambda: play_song(current_song, playlist, song_status))
play_btn.place(x=195, y=10)

resume_btn = Button(button_frame, text='Resume', bg='Aqua', font=("Georgia", 13), width=7,
                    command=lambda: resume_song(song_status))
resume_btn.place(x=285, y=10)

load_btn = Button(button_frame, text='Load Directory', bg='Aqua', font=("Georgia", 13), width=35, command=lambda: load(playlist))
load_btn.place(x=10, y=55)

# Label at the bottom that displays the state of the music
Label(root, textvariable=song_status, bg='SteelBlue', font=('Times', 9), justify=LEFT).pack(side=BOTTOM, fill=X)

# Finalizing the GUI
root.update()
root.mainloop()
run
python basicmusicplayer.py

A window appears with Load, Play, Pause, Stop, Resume buttons and an empty playlist. Click Load, pick your music folder, then double-click a song and press Play.

Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.

diagram Diagram mermaid
basicmusicplayer.py
import pygame.mixer as mixer
mixer.init()

mixer.init() sets up the audio output device. Until you call it, none of the mixer functions work. Calling it more than once is harmless on most systems.

basicmusicplayer.py
from tkinter import *
root = Tk()
root.title("Basic Music Player")
root.geometry("500x350")
  • Tk() creates the top-level window.
  • title sets the title-bar text.
  • geometry("WIDTHxHEIGHT") sets the size in pixels.
basicmusicplayer.py
song_frame = LabelFrame(root, text="Now Playing")
button_frame = LabelFrame(root, text="Controls")
listbox_frame = LabelFrame(root, text="Playlist")

A LabelFrame is a box with a title. Grouping related widgets makes the layout obvious to both the user and the developer.

basicmusicplayer.py
def play_song():
    song_name = playlist.get(ACTIVE)
    mixer.music.load(song_name)
    mixer.music.play()
    status_label.config(text="Playing")
 
def pause_song():
    mixer.music.pause()
    status_label.config(text="Paused")
 
def stop_song():
    mixer.music.stop()
    status_label.config(text="Stopped")
 
def resume_song():
    mixer.music.unpause()
    status_label.config(text="Playing")
  • playlist.get(ACTIVE) returns the currently highlighted entry in the listbox.
  • mixer.music.load(path) reads the file (no sound yet).
  • mixer.music.play() actually starts playback.
  • status_label.config(text=...) updates the label in real time.
basicmusicplayer.py
from tkinter import filedialog
import os
 
def load():
    directory = filedialog.askdirectory()
    os.chdir(directory)
    songs = os.listdir(directory)
    for song in songs:
        if song.lower().endswith((".mp3", ".wav")):
            playlist.insert(END, song)
  • filedialog.askdirectory() opens the OS file dialog. The function returns the chosen path (or an empty string if the user cancels).
  • os.chdir(directory) makes that folder the working directory so mixer.music.load(song) finds the file with just its name.
  • os.listdir(directory) returns every file/folder name as a list.
  • The if song.lower().endswith(...) filter ignores non-audio files like cover.jpg or desktop.ini.
basicmusicplayer.py
playlist = Listbox(listbox_frame, bg="black", fg="white", width=60, height=10)
playlist.pack()

A Listbox is the scrollable list on the left of the window. Items are added with playlist.insert(END, "song_name.mp3").

basicmusicplayer.py
root.mainloop()

This call never returns until the user closes the window. While it runs, Tkinter listens for clicks and dispatches them to your callbacks.

ProblemCauseFix
pygame.error: mixer not initializedForgot mixer.init()Call it once near the top
Window appears but no soundWrong working directory or unsupported formatos.chdir(directory) first; stick to MP3/WAV/OGG
Listbox is empty after LoadAll files filtered out by the extension checkPrint os.listdir(directory) to see what is there
Window freezes when clicking PlayLong-running code blocking the event loopAudio playback is non-blocking; if you add other long work, run it on a thread
_tkinter.TclError: invalid command nameUsed a widget after root.destroy()Do not touch widgets after the window closes
volume.py
def set_volume(value):
    mixer.music.set_volume(float(value) / 100)
 
Scale(root, from_=0, to=100, orient=HORIZONTAL, command=set_volume).pack()

Scale is Tkinter’s slider; its command is called with the new value as a string each time the user drags.

2. Display the current track in the title bar

Section titled “2. Display the current track in the title bar”
title.py
def play_song():
    song = playlist.get(ACTIVE)
    mixer.music.load(song); mixer.music.play()
    root.title(f"Now playing — {song}")
nav.py
def next_song():
    current = playlist.curselection()
    if not current: return
    new_index = (current[0] + 1) % playlist.size()
    playlist.selection_clear(0, END)
    playlist.selection_set(new_index)
    playlist.activate(new_index)
    play_song()
  • Shuffle: keep a shuffled copy of the indices using random.sample.
  • Repeat one: when the song ends, replay it. Detect end with pygame.mixer.music.get_busy() polled by root.after(1000, check).

Use the mutagen library (pip install mutagen) to read MP3 tags:

duration.py
from mutagen.mp3 import MP3
mp3 = MP3(filename)
duration_seconds = int(mp3.info.length)

Use tkinterDnD2 to drop files directly onto the playlist.

Write the current list of songs to a .txt or .json file and load it on start.

This script keeps everything at module level for simplicity. For a serious version, wrap state in a class:

oop.py
class MusicPlayer:
    def __init__(self, root):
        mixer.init()
        self.root = root
        self.build_ui()
 
    def build_ui(self):
        # create frames, buttons, listbox here
        ...
 
    def play(self):
        ...

Why bother? Because a class gives you:

  • self.current_song — clean state without globals.
  • Testability — instantiate and inspect without launching a window.
  • Reusability — embed the player as a widget inside a larger app.
  • Personal media players for offline music collections.
  • Kiosk or installation apps that loop background music.
  • Audio-book players with bookmark support.
  • White-noise and meditation apps.
  • The audio layer of a game (Pygame’s mixer is exactly the same API).

This project teaches:

  • GUI fundamentals — widgets, frames, packing, the event loop.
  • Event-driven thinking — code runs in response to actions, not top-to-bottom.
  • Working with the file system — directories, listings, extensions.
  • Multimedia I/O — using a binding (Pygame) to access the OS audio device.
  • Separation of UI and logic — even in 100 lines, callbacks isolate concerns.
  • Replace the basic Tkinter look with ttk widgets for native styling.
  • Add pygame.mixer.music.set_endevent to auto-advance to the next track.
  • Display album art by extracting it from MP3 tags with mutagen.
  • Stream audio over the network with pygame.mixer + a remote URL.
  • Port the same project to PyQt or CustomTkinter for a modern look.

You built a real desktop application with a window, controls, a playlist, and working sound — in roughly a hundred lines of Python. The same patterns (event loop, callbacks, widget composition) power every other Tkinter project on Python Central Hub. The full source is on GitHub. Try one of the variations above and ship your own customized version.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading