MP3播放器
效果
import threading
import traceback
import os
from ctypes import windll, c_buffer
import random
import time
'''
Inspired by (but not copied from) Michael Gundlach <gundlach@gmail.com>'s mp3play:
https://github.com/michaelgundlach/mp3play
在上位大神的基础上修改了其源码,并封装成类
'''
#_mci里有个!!异常捕捉,调试时必须开启
class _mci:
def __init__(self):
self.w32mci = windll.winmm.mciSendStringA
self.w32mcierror = windll.winmm.mciGetErrorStringA
def send(self, command):
buffer = c_buffer(255)
errorcode = self.w32mci(str(command).encode(), buffer, 254, 0)
if errorcode:
return errorcode, self.get_error(errorcode)
else:
return errorcode, buffer.value
def get_error(self, error):
error = int(error)
buffer = c_buffer(255)
self.w32mcierror(error, buffer, 254)
return buffer.value
def directsend(self, txt):
(err, buf) = self.send(txt)
if err != 0:
pass
#!!异常捕捉 print('Error %s for "%s": %s' % (str(err), txt, buf))
return (err, buf)
class AudioClip(object):
def __init__(self, filename):
filename = filename.replace('/', '\\')
self.filename = filename
self._alias = "mp3-1"
self._mci = _mci()
self._mci.directsend(r'open "%s" alias %s' % (filename, self._alias))
self._mci.directsend('set %s time format milliseconds' % self._alias)
err, buf = self._mci.directsend('status %s length' % self._alias)
self._length_ms = int(buf)
def volume(self, level):
"""Sets the volume between 0 and 100."""
self._mci.directsend('setaudio %s volume to %d' %
(self._alias, level * 10))
def start(self,filename):
filename = filename.replace('/', '\\')
self._mci.directsend(r'open "%s" alias %s' % (filename, self._alias))
self._mci.directsend('set %s time format milliseconds' % self._alias)
err, buf = self._mci.directsend('status %s length' % self._alias)
self._length_ms = int(buf)
def play(self, start_ms=None, end_ms=None):
start_ms = 0 if not start_ms else start_ms
end_ms = self._length_ms if not end_ms else end_ms
err, buf = self._mci.directsend('play %s from %d to %d'
% (self._alias, start_ms, end_ms))
return err, buf
def isplaying(self):
return self._mode() == 'playing'
def _mode(self):
# self._mci.directsend('pause %s' % self._alias)
# self._mci.directsend('resume %s' % self._alias)
err, buf = self._mci.directsend('status %s mode' % self._alias)
return buf
def pause(self):
self._mci.directsend('pause %s' % self._alias)
def resume(self):
self._mci.directsend('resume %s' % self._alias)
def ispaused(self):
return self._mode() == 'paused'
def stop(self):
self._mci.directsend('close %s' % self._alias)
# self._mci.directsend('stop %s' % self._alias)
# self._mci.directsend('seek %s to start' % self._alias)
def milliseconds(self):
return self._length_ms
# TODO: this closes the file even if we're still playing.
# no good. detect isplaying(), and don't die till then!
def __del__(self):
self._mci.directsend('close %s' % self._alias)
class MyMusic():
def __init__(self,music_set):
self.music_thread = None # 音乐线程,第三只手,用于检测状态,支持播放完后切换歌曲,检测用户输入,切换功能
self.mymusic = None
self.music_set = music_set # 音乐播放列表
self.music_set_del = set() # 已播放
self.type = "random" # 用户选择的播放类型 random,normal
self.mp3id = "" # mp3的ID,用于windll指令
self.state = None # mp3id音乐的当前状态
self.musicname = None # 将要播放的音乐名,实际上是这个音乐文件的路径
self.is_over = False # 音乐是否完毕,主线程里如果发现是true,可以提示是否再次打开线程
# mp3id音乐的用户操作None代表无操作,next代表切换歌曲指令,stop代表暂停
self.useraction = None
def start(self):
# 获取要播放的音乐文件名
self.get_music_filename()
# 创建对象播放音乐
self.mymusic = AudioClip(self.musicname)
self.mymusic.play()
self.get_state()
# 启动线程
self.music_thread = threading.Thread(target=self.__music_thread) # ,args=(self.music_set)
self.music_thread.start()
#----------- 主线程方法 ------------
# 由于只有主线程可以调用windll的指令,所以把用户的键盘输入放入到子线程中去了
def __music_thread(self):
#音乐线程,第三只手,用于检测状态,支持播放完后切换歌曲,检测用户输入,切换功能
while True:
music.useraction = input()
def get_music_filename(self):
# 通过type判断播放类型,拿出要播放的文件名!!
if self.type == "random":
musicname = random.choice(list(self.music_set - self.music_set_del)) # 删除已播放的
else:
musicname = list(self.music_set - self.music_set_del)[0]
self.music_set_del.add(musicname) # 存入已播放列表
self.musicname = musicname
print("正在播放 :" + musicname[musicname.rfind("\\") + 1:])
def get_state(self):
# 获取mp3id的状态
self.state = self.mymusic._mode()
return self.mymusic._mode()
def play(self):
self.mymusic._alias = "mp3-2"
self.mymusic = AudioClip(self.musicname)
self.mymusic.play()
def stop(self):
self.mymusic.stop()
def resume(self):
self.mymusic.resume()
def pause(self):
self.mymusic.pause()
def next(self):
if self.music_set - self.music_set_del:
self.stop()
self.get_music_filename()
self.play()
else:
print("已是最后一首了")
def replay(self):
self.stop()
# self.get_music_filename()
self.play()
def change_type(self):
if str(self.type) == "normal":
self.type = "random"
else:
self.type = "normal"
if __name__ == "__main__":
try:
print("--------自制MP3音乐播放器---------")
print("说明:")
print(" 指定文件夹路径: 直接 Enter 默认播放music文件夹下的音乐")
print("操作指南:")
print(" 输入p可暂停")
print(" 输入r可继续")
print(" 输入n可切换下一首歌曲")
print(" 输入c可切换播放模式")
print(" 输入RP可重新播放该曲")
print(" 关闭点击右上角叉叉")
print("-------------Enjoy------------")
file_path = ""
# file_path = input("请输入文件路径,或回车播放music下的音乐:")
if file_path == "":
file_path = os.getcwd()+"\\music\\"
# 检查文件中是否存在mp3文件并存入集合
myset = set()
for root, dirs, files in os.walk(file_path):
# print(root)
if root == file_path:
for f in files:
if f.endswith(".mp3"):
myset.add(file_path+f)
break
if len(myset) == 0:
print(file_path+"里没有找到mp3文件 T。T")
# music = MyMusic({"F:/pywork/fight/fight_music3/music/Fade-AlanWalker.mp3",
# "F:/pywork/fight/fight_music3/music/NobodyCanSaveMe-LinkinPark.mp3"})
while True:
music = MyMusic(myset)
print("当前播放模式:" + music.type)
print("播放文件目录:" + file_path)
music.start()
while True:
music.state = music.get_state() # 检测状态
time.sleep(1)
# 最后一首歌执行完毕跳出循环,结束线程,用户无法再输入
if (not music.music_set - music.music_set_del) and music.state == b'stopped':
print("音乐播放完毕")
break
# 播放完一首后自动播放下一首
if music.state == b'stopped' and music.useraction == None:
music.stop()
# 选择下一个要播放的文件
music.get_music_filename()
# 播放
music.play()
continue
# 指令!!
if music.useraction != None:
if music.useraction.upper() == "P":
music.pause()
elif music.useraction.upper() == "R":
music.resume()
elif music.useraction.upper() == "N":
music.next()
elif music.useraction.upper() == "C":
music.change_type()
print("当前播放模式:"+ music.type)
elif music.useraction.upper() == "RP":
music.replay()
music.useraction = None # 清空指令防止重复运行
time.sleep(1)
if input("是否要重新播放?y/n") == "y":
continue
else:
break
except SystemExit:
pass
except:
traceback.print_exc()
input()