我写了一个python音乐播放器,在实现进度条功能时
为什么拖动进度条后,进度条后会退回原来的位置,而不是在拖动到的位置继续加载啊
求个大佬解答啊(qmq)
这是我写的代码(有注释)
import os
import sys
import tkinter as tk
from tkinter import filedialog, messagebox
import pygame
import json
from moviepy.editor import AudioFileClip
import keyboard # 用于全局热键
import ctypes # 隐藏控制台窗口
import time # 用于时间逻辑
from PIL import Image, ImageTk # 用于图像处理
# 隐藏控制台窗口
def hide_console():
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
# 初始化 Pygame mixer
pygame.mixer.init()
# 加载配置
config_file = 'config.json'
def load_config():
if os.path.exists(config_file):
with open(config_file, 'r') as f:
return json.load(f)
return {"autostart": False}
def save_config(config):
with open(config_file, 'w') as f:
json.dump(config, f)
# 获取资源路径的函数
def resource_path(relative_path):
"""获取资源路径,兼容打包后引用"""
try:
base_path = sys._MEIPASS
except AttributeError:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
# 音乐文件转换器
class MusicFileConverter(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("音乐文件转换器")
self.geometry("400x200")
self.configure(bg="#222831")
self.selected_file = None
self.parent = parent # 父窗口引用
self.label = tk.Label(self, text="选择要转换的文件或文件夹", bg="#222831", fg="#00adb5", font=("Arial", 14))
self.label.pack(pady=10)
self.select_button = tk.Button(self, text="选择文件", command=self.select_file, bg="#00adb5", fg="white", font=("Arial", 12))
self.select_button.pack(pady=5)
self.folder_button = tk.Button(self, text="选择文件夹", command=self.select_folder, bg="#00adb5", fg="white", font=("Arial", 12))
self.folder_button.pack(pady=5)
self.convert_button = tk.Button(self, text="转换为 MP3", command=self.convert_to_mp3, bg="#00adb5", fg="white", font=("Arial", 12))
self.convert_button.pack(pady=20)
self.result_label = tk.Label(self, text="", bg="#222831", fg="white", font=("Arial", 10))
self.result_label.pack(pady=5)
def select_file(self):
"""选择文件"""
self.selected_file = filedialog.askopenfilename(filetypes=[
("音频文件", "*.wav;*.ogg;*.flac;*.m4a;*.mp4")
])
if self.selected_file:
self.label.config(text=f"选中: {self.selected_file}")
def select_folder(self):
"""选择文件夹"""
folder_path = filedialog.askdirectory()
if folder_path:
self.label.config(text=f"选中文件夹: {folder_path}")
self.selected_file = folder_path
def convert_to_mp3(self):
"""转换选中的文件为 MP3"""
if not self.selected_file:
messagebox.showwarning("警告", "请先选择文件或文件夹!")
return
if os.path.isfile(self.selected_file):
# 是文件的情况下
self.convert_single_file(self.selected_file)
elif os.path.isdir(self.selected_file):
# 是文件夹的情况下
for f in os.listdir(self.selected_file):
if not f.lower().endswith('.mp3'): # 只转换非MP3文件
full_path = os.path.join(self.selected_file, f)
if os.path.isfile(full_path):
self.convert_single_file(full_path)
def convert_single_file(self, file_path):
"""将单个文件转换为MP3"""
try:
base_name = os.path.splitext(os.path.basename(file_path))[0]
dir_name = os.path.dirname(file_path)
new_file_path = os.path.join(dir_name, f"{base_name}.mp3")
# 使用moviepy进行文件转换
audio_clip = AudioFileClip(file_path)
audio_clip.write_audiofile(new_file_path, codec='mp3')
audio_clip.close()
# 更新界面显示结果
self.result_label.config(text=f"成功转换: {new_file_path}")
# 添加到主程序的播放列表
self.parent.music_files.append(new_file_path) # 将MP3文件添加到主程序
self.parent.load_music() # 重新加载音乐文件
# 关闭转换器窗口
self.destroy()
except Exception as e:
messagebox.showerror("错误", f"无法转换文件 {file_path}: {e}")
# 创建主窗口
root = tk.Tk()
root.title("音径播放器")
root.geometry("400x500")
root.configure(bg="#222831") # 背景颜色
# 音乐播放状态
music_files = [] # 存储所有音乐文件
current_track = 0 # 当前播放的歌曲索引
loop_mode = False # False 为顺序播放,True 为单曲循环
is_playing = False # 记录播放/暂停状态
last_f6_time = 0 # 记录最后一次按下 F6 的时间
total_time = 0 # 总时间
rotation_angle = 0 # 旋转角度
# 新变量,用于表示音乐播放进度百分比
musictime = 0 # 进度条变量,范围从0到100
progress_value = 0 # 控制进度条的更新(0-100)
# 图标大小和位置设置
icon_size = 200 # 自定义旋转图标的大小
icon_x = 100 # 图标的X坐标
icon_y = 95 # 图标的Y坐标
# 加载图像(确保这些文件有透明背景)
play_image = tk.PhotoImage(file=resource_path("in/play.png")) # 播放图标
pause_image = tk.PhotoImage(file=resource_path("in/pause.png")) # 暂停图标
prev_image = tk.PhotoImage(file=resource_path("in/prev.png")) # 上一首图标
next_image = tk.PhotoImage(file=resource_path("in/next.png")) # 下一首图标
# 将下一首图标设为全局变量
next_button_image = next_image
rotating_icon_image = Image.open(resource_path("in/rotating_icon.png")).resize((icon_size, icon_size)) # 加载旋转图标并自定义大小
# 全局变量,用于跟踪进度条拖动状态
is_dragging = False # 标记是否正在拖动进度条
def load_music():
"""打开文件对话框,选择音乐文件"""
global music_files, current_track, total_time
file_path = filedialog.askopenfilename(filetypes=[
("音频文件", "*.mp3;*.wav;*.ogg;*.flac;*.m4a;*.mp4")
])
if file_path:
music_files.append(file_path) # 直接更新为选择的文件
if music_files:
try:
load_current_track()
messagebox.showinfo("音乐加载", "音乐已加载!可以开始播放。")
except Exception as e:
messagebox.showerror("错误", f"无法加载音乐文件: {e}")
def load_music_folder():
"""打开文件夹对话框,选择音乐文件夹"""
global music_files, current_track
folder_path = filedialog.askdirectory() # 选择文件夹
if folder_path:
music_files = []
# 仅加载MP3文件
for f in os.listdir(folder_path):
full_path = os.path.join(folder_path, f)
if f.lower().endswith('.mp3'):
music_files.append(full_path) # 添加MP3文件
if music_files:
current_track = 0 # 重置播放索引
load_current_track()
messagebox.showinfo("文件夹加载", f"已加载 {len(music_files)} 首音乐!")
else:
messagebox.showwarning("警告", "所选文件夹中没有支持的音乐文件。")
def load_current_track():
"""加载当前播放的音乐"""
global total_time # 确保使用全局变量
if music_files and 0 <= current_track < len(music_files):
music_file = music_files[current_track]
try:
pygame.mixer.music.load(music_file) # 直接加载MP3文件
except pygame.error as e:
messagebox.showerror("错误", f"无法加载音乐文件 {music_file}: {e}")
return # 如果加载失败,则退出此函数
total_time = AudioFileClip(music_files[current_track]).duration # 获取音乐总长度
total_time_label.config(text=f"总时间: {format_time(total_time)}") # 更新总时间标签
# 获取当前播放位置,设置为0(音乐未播放,不需要设置位置)
current_time = 0
current_time_label.config(text=f"当前时间: {format_time(current_time)}") # 更新当前时间标签为实际时间
# 播放当前歌曲
play_music() # 播放当前歌曲
display_song_name() # 显示歌曲名称
stop_rotating() # 停止旋转图标
def format_time(seconds):
"""将秒转换为HH:MM:SS格式"""
minutes, seconds = divmod(int(seconds), 60)
hours, minutes = divmod(minutes, 60)
return f"{hours:02}:{minutes:02}:{seconds:02}"
def play_music():
"""播放音乐"""
global is_playing
if music_files:
pygame.mixer.music.play() # 播放音乐
is_playing = True
play_pause_button.config(image=pause_image) # 切换到暂停图标
start_rotating() # 开始旋转图标
update_progress() # 开始更新时间
# 增加播放完成的检测
root.after(100, check_playing_status) # 每100ms检查播放状态
def check_playing_status():
"""检查当前音乐是否播放完毕"""
global current_track
if not pygame.mixer.music.get_busy(): # 当音乐播放完毕
if loop_mode:
# 如果是单曲循环,则重新播放当前曲目
load_current_track()
else:
# 如果是顺序播放,切换到下一首曲目
next_track() # 自动切换到下一曲
else:
root.after(100, check_playing_status) # 继续检查播放状态
def pause_music():
"""暂停音乐"""
global is_playing
pygame.mixer.music.pause()
is_playing = False
play_pause_button.config(image=play_image) # 切换到播放图标
stop_rotating() # 停止旋转图标
def unpause_music():
"""恢复音乐"""
global is_playing
if music_files:
pygame.mixer.music.unpause()
is_playing = True
play_pause_button.config(image=pause_image) # 切换为暂停图标
start_rotating() # 开始旋转图标
update_progress() # 继续更新时间
def close_application():
"""关闭应用程序"""
pygame.mixer.music.stop() # 停止音乐
root.destroy()
def toggle_loop_mode():
"""切换播放模式"""
global loop_mode
loop_mode = not loop_mode
mode = "单曲循环" if loop_mode else "顺序播放"
messagebox.showinfo("播放模式", f"当前播放模式: {mode}")
def handle_f6_key():
"""处理 F6 键逻辑"""
global last_f6_time, is_playing
current_time = time.time()
if (current_time - last_f6_time) <= 1:
play_music() # 重新播放音乐
else:
if not is_playing:
unpause_music() # 恢复播放
else:
pause_music() # 暂停音乐
last_f6_time = current_time
def previous_track():
"""切换到上一首歌曲"""
global current_track
if music_files:
if current_track > 0:
current_track -= 1
else:
messagebox.showinfo("无效操作", "已经是第一首歌曲,无法再切换到上一首。")
pause_music() # 在切换之前先暂停音乐
root.after(1000, load_current_track) # 1秒后加载当前曲目并播放
def next_track():
"""切换到下一首歌曲"""
global current_track
if music_files:
if loop_mode:
current_track += 1
if current_track >= len(music_files):
current_track = 0 # 如果超过最后一首,则回到第一首
else:
if current_track < len(music_files) - 1:
current_track += 1 # 切换到下一首
else:
current_track = 0 # 如果到达最后一首,则循环回到第一首
pause_music() # 在切换之前先暂停音乐
root.after(1000, load_current_track) # 1秒后加载当前曲目并播放
def toggle_window():
"""切换窗口的显示与隐藏"""
if root.state() == 'normal':
root.withdraw() # 隐藏窗口
else:
root.deiconify() # 显示窗口
root.lift() # 保证窗口在最上面
root.attributes('-topmost', True) # 固定在最上层
# 更新进度条和当前播放时间
def update_progress():
"""更新播放状态和进度条"""
global musictime, progress_value, total_time
if is_playing and not is_dragging: # 只有在不拖动时更新进度
current_time = pygame.mixer.music.get_pos() / 1000 # 获取当前播放的时间(单位:秒)
musictime = (current_time / total_time) * 100 if total_time > 0 else 0 # 更新旧变量的百分比
# 将旧变量的值实时设置到新变量上
progress_value = musictime # 将旧变量值赋给新变量
progress_bar.set(progress_value) # 更新进度条的值
current_time_label.config(text=f"当前时间: {format_time(current_time)}") # 更新当前时间标签
root.after(1000, update_progress) # 每秒更新状态
# 旋转图标相关
rotation_angle = 0 # 旋转角度
icon_label = tk.Label(root, image=ImageTk.PhotoImage(rotating_icon_image), bg="#222831") # 创建旋转图标标签
icon_label.place(x=icon_x, y=icon_y) # 图标初始位置
def rotate_icon():
"""旋转图标"""
global rotation_angle
rotation_angle = (rotation_angle + 5) % 360 # 旋转角度
rotated_icon = rotating_icon_image.rotate(rotation_angle) # 旋转图标
icon_label.img = ImageTk.PhotoImage(rotated_icon) # 更新图像
icon_label.configure(image=icon_label.img) # 显示旋转后的图标
if is_playing: # 只有在播放时才继续旋转
root.after(20, rotate_icon) # 每20毫秒旋转一次
def start_rotating():
"""开始旋转图标"""
rotate_icon() # 启动旋转
def stop_rotating():
"""停止旋转图标"""
icon_label.configure(image=ImageTk.PhotoImage(rotating_icon_image)) # 恢复到原始图标
def display_song_name():
"""显示歌曲名称"""
if music_files and 0 <= current_track < len(music_files):
song_name = os.path.splitext(os.path.basename(music_files[current_track]))[0] # 获取文件名,不包括后缀
song_name_label.config(text=f"歌曲名称: {song_name}")
# 创建 UI 组件
title_label = tk.Label(root, text="音径播放器", bg="#222831", fg="#00adb5", font=("Arial", 24, "bold"))
title_label.place(x=120, y=20) # x坐标居中
# 转换器按钮
convert_btn = tk.Button(root, text="转换音频文件", command=lambda: MusicFileConverter(root), bg="#00adb5", fg="white", font=("Arial", 10))
convert_btn.place(x=10, y=30) # 左上角
# “上一首”图标
prev_button = tk.Label(root, image=prev_image, bg="#222831")
prev_button.place(x=100, y=370) # 使用place方法定位
prev_button.bind("
# 播放/暂停按钮
play_pause_button = tk.Button(root, image=play_image, command=handle_f6_key, bg="#222831", borderwidth=0)
play_pause_button.place(x=160, y=350)
# “下一首”图标
next_button = tk.Label(root, image=next_button_image, bg="#222831")
next_button.place(x=260, y=370) # 使用place方法定位
next_button.bind("
# 加载音乐按钮,居中显示
load_btn = tk.Button(root, text="加载音乐", command=load_music, bg="#00adb5", fg="white", font=("Arial", 14))
load_btn.place(x=150, y=450) # 居中显示
# 加载文件夹按钮
load_folder_btn = tk.Button(root, text="加载文件夹", command=load_music_folder, bg="#00adb5", fg="white", font=("Arial", 14))
load_folder_btn.place(x=8, y=450) # 将加载文件夹按钮放在左侧
# 切换播放模式按钮
loop_btn = tk.Button(root, text="切换播放模式", command=toggle_loop_mode, bg="#00adb5", fg="white", font=("Arial", 14))
loop_btn.place(x=260, y=450) # 将循环播放按钮放在原“自启动”按钮的位置
# 当前时间标签
current_time_label = tk.Label(root, text="当前时间: 00:00:00", bg="#222831", fg="white")
current_time_label.place(x=10, y=420) # 使用place方法定位
# 总时间标签
total_time_label = tk.Label(root, text="总时间: 00:00:00", bg="#222831", fg="white")
total_time_label.place(x=290, y=420) # 使用place方法定位
# 歌曲名称标签
song_name_label = tk.Label(root, text="歌曲名称: ", bg="#222831", fg="white")
song_name_label.place(x=130, y=70) # 在播放按钮下方显示
# 音乐进度条
progress_bar = tk.Scale(root, from_=0, to=100, orient=tk.HORIZONTAL, bg="#00adb5", fg="white", sliderlength=15, cursor="hand2")
progress_bar.place(x=50, y=300, width=300) # 设置滑块的位置信息
# 更新音乐播放位置时
def set_music_position(event):
"""设置音乐播放的位置,以进度条的值为基准"""
global total_time
if total_time > 0:
new_position_seconds = (progress_bar.get() / 100) * total_time # 获取进度条值对应的位置
pygame.mixer.music.set_pos(new_position_seconds) # 设置音乐播放位置
# 更新当前时间标签
current_time_label.config(text=f"当前时间: {format_time(new_position_seconds)}") # 更新当前时间标签
# 处理鼠标按下事件
def on_progress_bar_click(event):
global is_dragging
is_dragging = True # 标记为正在拖动
set_music_position(event) # 更新播放位置
# 处理鼠标松开事件
def on_progress_bar_release(event):
global is_dragging # 确保使用全局变量
is_dragging = False # 取消拖动标记
set_music_position(event) # 在释放鼠标时更新位置
current_time_seconds = (progress_bar.get() / 100) * total_time # 计算拖动后的当前歌曲时间
current_time_label.config(text=f"当前时间: {format_time(current_time_seconds)}") # 更新当前时间标签
# 重新开始更新时间的函数
update_progress(musictime) # 从当前位置继续更新进度
# 绑定滑块的点击和释放事件
progress_bar.bind("
progress_bar.bind("
def bind_global_keys():
"""绑定全局热键"""
keyboard.add_hotkey('f5', toggle_window) # 控制窗口的显示与隐藏
keyboard.add_hotkey('f6', handle_f6_key) # 控制播放和暂停
# 隐藏控制台窗口
hide_console()
# 绑定全局热键
bind_global_keys()
# 运行主循环
start_rotating() # 启动旋转
update_progress() # 启动进度更新
root.mainloop()
# 退出 pygame
pygame.mixer.quit()