Build a Stunning Python Music Player with PyQt5 – Step‑by‑Step Guide

This tutorial walks you through creating a feature‑rich desktop music player in Python using PyQt5, covering UI layout design, keyword‑based music crawling, multithreaded downloading, playback controls, volume adjustment, and additional functions like random and repeat modes, all illustrated with complete code snippets and screenshots.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Build a Stunning Python Music Player with PyQt5 – Step‑by‑Step Guide

Preface

While coding and listening to music at night, many songs require a copyright notice and cannot be played directly. To solve this, we will build a cool music player with Python.

The final effect of the player is shown below.

1. Core Feature Design

The UI layout is divided into three parts: a top bar for keyword search and source selection, a middle area showing album art on the left and the music list on the right, and a bottom bar with playback controls, progress bar, volume, and navigation buttons.

Top bar includes keyword search, source selection, and window controls (minimize, maximize, close).

Middle area displays album cover on the left and music list on the right.

Bottom bar shows current track, progress bar, volume control, previous/next, play/pause, and playback mode.

Keyword Music List Crawler

Based on the entered keyword and selected source, the crawler fetches music data, including title, author, download URL, cover image, and lyrics, using multithreading. Core code:

def run(self):
    qmut.lock()
    try:
        global paing, stop, lrcs, urls, songs, name, songid, proxies, pic, tryed
        paing = True
        print('搜索软件{}'.format(type))
        print('开始搜索')
        name = name
        headers = {
            'User-Agent': 'Mozilla/5.0 ...',
            'X-Requested-With': 'XMLHttpRequest'
        }
        urls = []
        songs = []
        pic = []
        lrcs = []
        pages = 5
        for a in range(0, pages):
            if not stop:
                urlss = ['http://music.9q4.cn/', 'https://defcon.cn/dmusic/', 'http://www.xmsj.org/', 'http://music.laomao.me/']
                if tryed > 3:
                    tryed = 0
                url = urlss[tryed]
                params = {'input': name, 'filter': 'name', 'type': type, 'page': a}
                if not stop:
                    try:
                        res = post(url, params, headers=headers, proxies=proxies)
                        html = res.json()
                        for i in range(0, 10):
                            try:
                                title = jsonpath(html, '$..title')[i]
                                author = jsonpath(html, '$..author')[i]
                                url1 = jsonpath(html, '$..url')[i]
                                pick = jsonpath(html, '$..pic')[i]
                                lrc = jsonpath(html, '$..lrc')[i]
                                print(title, author)
                                lrcs.append(lrc)
                                urls.append(url1)
                                pic.append(pick)
                                songs.append(str(title) + ' - ' + str(author))
                            except:
                                pass
                    except:
                        stop = False
                        paing = False
                        self.trigger.emit(str('finish'))
                else:
                    self.trigger.emit(str('clear'))
        stop = False
        paing = False
    except:
        print('爬取歌曲出错')
        self.trigger.emit(str('unfinish'))
        stop = False
        paing = False
    qmut.unlock()

After crawling, the music list is displayed in the search page.

def repite(self, name, type):
    global tryed, paing
    self.listwidget.clear()
    self.listwidget.addItem('搜索中')
    self.listwidget.item(0).setForeground(Qt.white)
    try:
        if paing:
            stop = True
            self.listwidget.clear()
            self.work2 = PAThread()
            self.work2.start()
            self.work2.trigger.connect(self.seafinish)
        else:
            self.work2 = PAThread()
            self.work2.start()
            self.work2.trigger.connect(self.seafinish)
    except:
        tryed = tryed + 1
        get_info('https://www.kuaidaili.com/free/inha')
        self.listwidget.addItem('貌似没网了呀`(*>﹏<*)′,再试一遍吧~')
        self.listwidget.item(0).setForeground(Qt.white)

Music Playback

When a track is double‑clicked, the player downloads the file using multithreading and plays it. Core playback thread:

class WorkThread(QThread):
    trigger = pyqtSignal(str)
    def __init__(self):
        super(WorkThread, self).__init__()
    def cbk(self, a, b, c):
        per = 100.0 * a * b / c
        if per > 100:
            per = 100
        self.trigger.emit(str('%.2f%%' % per))
    def run(self):
        try:
            global number, path, downloading
            proxies = {'http': 'http://124.72.109.183:8118', 'https': 'http://49.85.1.79:31666'}
            headers = {'User-Agent': 'Mozilla/5.0 ...', 'X-Requested-With': 'XMLHttpRequest'}
            try:
                aq = pic[num]
                aqq = aq.split('/')
                if type == 'kugou' and len(aqq)-1 == 6:
                    aqqe = f"{aqq[0]}//{aqq[2]}/{aqq[3]}/400/{aqq[5]}/{aqq[6]}"
                elif type == 'netease' and len(aqq)-1 == 4:
                    aqn = aq.split('?')
                    aqqe = aqn[0] + '?param=500x500'
                else:
                    aqqe = pic[num]
                req = get(aqqe)
                checkfile = open(data + '/ls1.png', 'w+b')
                for i in req.iter_content(100000):
                    checkfile.write(i)
                checkfile.close()
                url1 = urls[num]
                number += 1
                path = data + f'\{number}.临时文件'
                headers = {'User-Agent': 'Mozilla/5.0 ...', 'X-Requested-With': 'XMLHttpRequest'}
                with get(url1, stream=True, headers=headers) as r, open(path, 'wb') as file:
                    total_size = int(r.headers['content-length'])
                    content_size = 0
                    for content in r.iter_content(chunk_size=1024):
                        file.write(content)
                        content_size += len(content)
                        plan = (content_size / total_size) * 100
                        self.trigger.emit(str(int(plan)) + '%')
                to = f'downloadmusic\{songs[num]}.mp3'
                makedirs('downloadmusic', exist_ok=True)
                copyfile(path, to)
                downloading = False
                self.trigger.emit('finish')
            except:
                self.trigger.emit('nofinish')
        except:
            self.trigger.emit('nofinish')

The playback function loads the temporary file, updates UI labels, shows album art, and starts the mixer:

def bofang(self, num, bo):
    try:
        import urllib
        global pause, songs, music, downloading
        downloading = True
        self.console_button_3.setIcon(icon('fa.pause', color='#F76677', font=18))
        pause = False
        mixer.stop()
        mixer.init()
        self.Timer = QTimer()
        self.Timer.start(500)
        self.label.setText('正在寻找文件...')
        self.work = WorkThread()
        self.work.start()
        self.work.trigger.connect(self.display)
    except:
        sleep(0.1)
        print('播放系统错误')

The display method handles different signals (finish, nofinish, progress) and updates the UI, loads the MP3 with mixer.music.load, and starts playback.

Additional Features

Beyond basic playback, the player supports random play, previous/next track, single‑track repeat, and playback mode switching (sequential → random → repeat). Example of random play:

def shui(self):
    global num, songs
    if bo == 'boing':
        q = len(songs) - 1
        num = randint(1, q)
    elif bo == 'love':
        q = len(loves) - 1
        num = randint(1, q)
    else:
        q = len(songed) - 1
        num = randint(0, q)
    try:
        print('随机播放下一首')
        mixer.init()
        self.Timer = QTimer()
        self.Timer.start(500)
        if bo == 'boing':
            self.label.setText(songs[num])
        elif bo == 'love':
            self.label.setText(loves[num])
        else:
            self.label.setText(songed[num])
        self.bofang(num, bo)
    except:
        pass

Previous and next track functions adjust the index and call bofang again. Single‑track repeat is handled by the always method, which re‑plays the current track.

Playback mode switching cycles through sequential, random, and repeat modes, updating the label and button icons accordingly.

Conclusion

The music player now supports searching, downloading, playing, and basic controls. Future improvements include local music saving, local playback, favorites, lyric display, and seeking within tracks.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GUImultithreadingAudio PlaybackWeb Scrapingmusic player
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.