用Python加ai写了一个本地音乐播放器

2025-03-11 172 0

今天把所有事情忙完后,自己想下载一些高质量的歌曲然后用独立播放器播放。然后用Python写了一个简单的播放器,大家可以下载下来运行一下或者大家自己也有需要可以下载下来自己修改一下或者独立打包运行即可。

运行演示:

成品代码展示:

import tkinter as tk
from tkinter import messagebox, filedialog
import os
import pygame
import random
class MusicPlayer:
def __init__(self, root):
self.root = root
self.root.title("音乐播放器")
self.root.geometry("600x400")  # 调整窗口大小,以适应两个列表框
# 初始化pygame mixer
pygame.mixer.init()
pygame.mixer.music.set_endevent(pygame.USEREVENT)  # 设置结束事件
# 音乐文件列表
self.music_files = []
self.current_song_index = 0
self.is_playing = False
self.song_lengths = []  # 保存每首歌的长度
# 播放模式(0: 单曲循环, 1: 随机播放)
self.play_mode = 0
# 创建界面组件
self.create_widgets()
# 定时器用于更新进度条
self.update_timer = None
def create_widgets(self):
# 文件播放路径显示
self.current_song_label = tk.Label(self.root, text="当前播放: ", font=("Arial", 12))
self.current_song_label.pack(pady=10)
# 按钮框架
self.button_frame = tk.Frame(self.root)
self.button_frame.pack(pady=5)
# 添加音频文件按钮
self.add_audio_button = tk.Button(self.button_frame, text="添加音频文件", command=self.add_audio)
self.add_audio_button.grid(row=0, column=0, padx=5)
# 移除音频文件按钮
self.remove_audio_button = tk.Button(self.button_frame, text="移除选中音频", command=self.remove_audio)
self.remove_audio_button.grid(row=0, column=1, padx=5)
# 创建水平分割线
self.splitter_frame = tk.Frame(self.root)
self.splitter_frame.pack(pady=5)
# 列表框用于显示音乐文件
self.music_listbox = tk.Listbox(self.splitter_frame, selectmode=tk.SINGLE, width=30)
self.music_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.music_listbox.bind('<<ListboxSelect>>', self.on_select)
# 按钮框架
self.control_frame = tk.Frame(self.root)
self.control_frame.pack(pady=10)
# 上一首
self.prev_button = tk.Button(self.control_frame, text="上一首", command=self.play_prev_song)
self.prev_button.grid(row=0, column=0, padx=5)
# 播放/暂停
self.play_button = tk.Button(self.control_frame, text="播放", command=self.toggle_play)
self.play_button.grid(row=0, column=1, padx=5)
# 下一首
self.next_button = tk.Button(self.control_frame, text="下一首", command=self.play_next_song)
self.next_button.grid(row=0, column=2, padx=5)
# 播放模式切换按钮
self.mode_button = tk.Button(self.control_frame, text="单曲循环", command=self.toggle_play_mode)
self.mode_button.grid(row=0, column=3, padx=5)
# 音量调节
self.volume_label = tk.Label(self.control_frame, text="音量:")
self.volume_label.grid(row=0, column=4, padx=5)
self.volume_scale = tk.Scale(self.control_frame, from_=0, to=1, resolution=0.1, orient=tk.HORIZONTAL,
command=self.set_volume)
self.volume_scale.set(0.5)  # 初始音量为50%
self.volume_scale.grid(row=0, column=5, padx=5)
# 播放进度条
self.progress_scale = tk.Scale(self.root, from_=0, to=100, orient=tk.HORIZONTAL, length=400, label="播放进度",
resolution=1)
self.progress_scale.pack(pady=5)
self.progress_scale.bind("<ButtonRelease-1>", self.seek_music)
# 播放进度显示总时长
self.progress_time_label = tk.Label(self.root, text="0:00/0:00", font=("Arial", 10))
self.progress_time_label.pack(pady=5)
def add_audio(self):
# 打开文件对话框选择音频文件
file_paths = filedialog.askopenfilenames(filetypes=[("音频文件", "*.mp3 *.wav")])
for file_path in file_paths:
# 添加选择的音频文件到音乐列表
if file_path not in self.music_files:
self.music_files.append(file_path)  # 保存完整路径
self.music_listbox.insert(tk.END, os.path.basename(file_path))  # 在列表框中显示文件名
# 计算每一首歌的长度并保存
try:
sound = pygame.mixer.Sound(file_path)
self.song_lengths.append(sound.get_length())
except pygame.error:
self.song_lengths.append(0)
if self.music_files:
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}")
self.update_progress_bar_max()
def remove_audio(self):
# 移除选中音频
selection = self.music_listbox.curselection()
if selection:
index = selection[0]
if index == self.current_song_index:
messagebox.showwarning("警告", "当前正在播放的歌曲不能被移除。")
return
removed_song = self.music_files.pop(index)  # 移除歌曲
self.song_lengths.pop(index)  # 移除对应歌曲的长度
self.music_listbox.delete(index)  # 从列表框中删除
if self.current_song_index >= index:
self.current_song_index = max(0, self.current_song_index - 1)
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}" if self.music_files else "当前播放: ")
messagebox.showinfo("移除音频", f"已移除选择的音频: {os.path.basename(removed_song)}")
def on_select(self, event):
# 处理列表框选中项的更改
selection = self.music_listbox.curselection()
if selection:
self.current_song_index = selection[0]
self.play_music()
def play_music(self):
if self.music_files:
pygame.mixer.music.load(self.music_files[self.current_song_index])  # 使用全路径加载音乐
pygame.mixer.music.play()
self.is_playing = True
self.play_button.config(text="暂停")
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}")  # 更新显示当前歌曲
self.update_progress_bar_max()
self.update_progress_bar()
def toggle_play(self):
if self.is_playing:
pygame.mixer.music.pause()
self.is_playing = False
self.play_button.config(text="播放")
self.root.after_cancel(self.update_timer)  # 取消定时器
else:
pygame.mixer.music.unpause()
self.is_playing = True
self.play_button.config(text="暂停")
self.update_progress_bar()  # 恢复进度条更新
def play_next_song(self):
if self.music_files:  # 确保列表不为空
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)
else:
self.current_song_index = (self.current_song_index + 1) % len(self.music_files)
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.play_music()
def play_prev_song(self):
if self.music_files:  # 确保列表不为空
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)
else:
self.current_song_index = (self.current_song_index - 1) % len(self.music_files)
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.play_music()
def toggle_play_mode(self):
if self.play_mode == 0:
self.play_mode = 1
self.mode_button.config(text="随机播放")
else:
self.play_mode = 0
self.mode_button.config(text="单曲循环")
def set_volume(self, volume):
pygame.mixer.music.set_volume(float(volume))
def update_progress_bar(self):
if self.is_playing:
current_pos = pygame.mixer.music.get_pos() / 1000.0  # 获取当前播放位置(秒)
self.progress_scale.set(current_pos)
# 更新显示的时间为分钟
total_length = self.song_lengths[self.current_song_index]  # 获取歌曲总长度(秒)
minutes_elapsed = int(current_pos // 60)
seconds_elapsed = int(current_pos % 60)
minutes_total = int(total_length // 60)
seconds_total = int(total_length % 60)
self.progress_time_label.config(
text=f"{minutes_elapsed}:{seconds_elapsed:02}/{minutes_total}:{seconds_total:02}")  # 格式化显示时间
self.update_timer = self.root.after(100, self.update_progress_bar)  # 每100毫秒更新一次进度条
def update_progress_bar_max(self):
if self.music_files:
total_length = self.song_lengths[self.current_song_index]  # 获取歌曲总长度(秒)
self.progress_scale.config(to=total_length)
def seek_music(self, event):
position = self.progress_scale.get()  # 获取当前进度条的位置
pygame.mixer.music.set_pos(position)  # 设置播放位置
def handle_music_end(self):
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index
self.play_music()
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)
self.play_music()
else:
self.current_song_index = (self.current_song_index + 1) % len(self.music_files)
self.play_music()
def update(self):
for event in pygame.event.get():
if event.type == pygame.USEREVENT:  # 检测音乐结束事件
self.handle_music_end()
self.root.after(100, self.update)  # 每100毫秒检查一次事件
if __name__ == "__main__":
root = tk.Tk()
player = MusicPlayer(root)
root.after(100, player.update)  # 启动事件循环检查
root.mainloop()

注释学习版代码:

import tkinter as tk  # 导入tkinter库,用于创建GUI界面
from tkinter import messagebox, filedialog  # 导入tkinter的messagebox和filedialog模块,用于显示消息框和文件对话框
import os  # 导入os库,用于处理文件路径等操作系统相关功能
import pygame  # 导入pygame库,用于处理音频播放
import random  # 导入random库,用于生成随机数
class MusicPlayer:  # 定义MusicPlayer类,用于创建音乐播放器
def __init__(self, root):  # 初始化MusicPlayer类的方法,root是tkinter的主窗口
self.root = root  # 将tkinter主窗口赋值给self.root
self.root.title("音乐播放器")  # 设置窗口标题为“音乐播放器”
self.root.geometry("600x400")  # 设置窗口大小为600x400像素
# 初始化pygame mixer
pygame.mixer.init()  # 初始化pygame的mixer模块,用于音频播放
pygame.mixer.music.set_endevent(pygame.USEREVENT)  # 设置音乐播放结束时触发的事件类型为pygame.USEREVENT
# 音乐文件列表
self.music_files = []  # 创建一个空列表,用于存储音乐文件的路径
self.current_song_index = 0  # 设置当前播放歌曲的索引为0
self.is_playing = False  # 设置当前是否正在播放音乐的标志为False
self.song_lengths = []  # 创建一个空列表,用于存储每首歌的长度
# 播放模式(0: 单曲循环, 1: 随机播放)
self.play_mode = 0  # 设置默认播放模式为单曲循环
# 创建界面组件
self.create_widgets()  # 调用create_widgets方法,创建窗口中的各种组件
# 定时器用于更新进度条
self.update_timer = None  # 创建一个定时器变量,初始为None
def create_widgets(self):  # 创建界面组件的方法
# 文件播放路径显示
self.current_song_label = tk.Label(self.root, text="当前播放: ", font=("Arial", 12))  # 创建一个标签,显示当前播放的歌曲
self.current_song_label.pack(pady=10)  # 将标签添加到窗口中,并设置垂直填充距离为10像素
# 按钮框架
self.button_frame = tk.Frame(self.root)  # 创建一个框架,用于容纳按钮
self.button_frame.pack(pady=5)  # 将框架添加到窗口中,并设置垂直填充距离为5像素
# 添加音频文件按钮
self.add_audio_button = tk.Button(self.button_frame, text="添加音频文件", command=self.add_audio)  # 创建一个按钮,用于添加音频文件
self.add_audio_button.grid(row=0, column=0, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 移除音频文件按钮
self.remove_audio_button = tk.Button(self.button_frame, text="移除选中音频", command=self.remove_audio)  # 创建一个按钮,用于移除选中的音频文件
self.remove_audio_button.grid(row=0, column=1, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 创建水平分割线
self.splitter_frame = tk.Frame(self.root)  # 创建一个框架,用于分割界面
self.splitter_frame.pack(pady=5)  # 将框架添加到窗口中,并设置垂直填充距离为5像素
# 列表框用于显示音乐文件
self.music_listbox = tk.Listbox(self.splitter_frame, selectmode=tk.SINGLE, width=30)  # 创建一个列表框,用于显示音乐文件列表
self.music_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # 将列表框添加到框架中,并设置填充和扩展属性
self.music_listbox.bind('<<ListboxSelect>>', self.on_select)  # 绑定列表框选中事件到on_select方法
# 按钮框架
self.control_frame = tk.Frame(self.root)  # 创建一个框架,用于容纳控制按钮
self.control_frame.pack(pady=10)  # 将框架添加到窗口中,并设置垂直填充距离为10像素
# 上一首
self.prev_button = tk.Button(self.control_frame, text="上一首", command=self.play_prev_song)  # 创建一个按钮,用于播放上一首歌曲
self.prev_button.grid(row=0, column=0, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 播放/暂停
self.play_button = tk.Button(self.control_frame, text="播放", command=self.toggle_play)  # 创建一个按钮,用于播放或暂停音乐
self.play_button.grid(row=0, column=1, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 下一首
self.next_button = tk.Button(self.control_frame, text="下一首", command=self.play_next_song)  # 创建一个按钮,用于播放下一首歌曲
self.next_button.grid(row=0, column=2, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 播放模式切换按钮
self.mode_button = tk.Button(self.control_frame, text="单曲循环", command=self.toggle_play_mode)  # 创建一个按钮,用于切换播放模式
self.mode_button.grid(row=0, column=3, padx=5)  # 将按钮添加到框架中,并设置网格位置和水平填充距离
# 音量调节
self.volume_label = tk.Label(self.control_frame, text="音量:")  # 创建一个标签,用于显示音量调节的文字
self.volume_label.grid(row=0, column=4, padx=5)  # 将标签添加到框架中,并设置网格位置和水平填充距离
self.volume_scale = tk.Scale(self.control_frame, from_=0, to=1, resolution=0.1, orient=tk.HORIZONTAL,
command=self.set_volume)  # 创建一个滑动条,用于调节音量
self.volume_scale.set(0.5)  # 设置音量滑动条的初始值为0.5(即50%的音量)
self.volume_scale.grid(row=0, column=5, padx=5)  # 将滑动条添加到框架中,并设置网格位置和水平填充距离
# 播放进度条
self.progress_scale = tk.Scale(self.root, from_=0, to=100, orient=tk.HORIZONTAL, length=400, label="播放进度",
resolution=1)  # 创建一个进度条,用于显示播放进度
self.progress_scale.pack(pady=5)  # 将进度条添加到窗口中,并设置垂直填充距离为5像素
self.progress_scale.bind("<ButtonRelease-1>", self.seek_music)  # 绑定进度条释放鼠标左键事件到seek_music方法
# 播放进度显示总时长
self.progress_time_label = tk.Label(self.root, text="0:00/0:00", font=("Arial", 10))  # 创建一个标签,用于显示播放时间进度和总时长
self.progress_time_label.pack(pady=5)  # 将标签添加到窗口中,并设置垂直填充距离为5像素
def add_audio(self):  # 添加音频文件的方法
# 打开文件对话框选择音频文件
file_paths = filedialog.askopenfilenames(filetypes=[("音频文件", "*.mp3 *.wav")])  # 弹出文件对话框,选择多个音频文件,只允许mp3和wav格式
for file_path in file_paths:  # 遍历选择的文件路径列表
# 添加选择的音频文件到音乐列表
if file_path not in self.music_files:  # 如果文件路径不在音乐文件列表中
self.music_files.append(file_path)  # 将文件路径添加到音乐文件列表
self.music_listbox.insert(tk.END, os.path.basename(file_path))  # 将文件名添加到列表框中
# 计算每一首歌的长度并保存
try:
sound = pygame.mixer.Sound(file_path)  # 尝试加载音频文件
self.song_lengths.append(sound.get_length())  # 获取音频文件的长度并添加到歌曲长度列表
except pygame.error:
self.song_lengths.append(0)  # 如果加载失败,将长度设为0
if self.music_files:  # 如果音乐文件列表不为空
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}")  # 更新当前播放歌曲的标签
self.update_progress_bar_max()  # 更新进度条的最大值
def remove_audio(self):  # 移除音频文件的方法
# 移除选中音频
selection = self.music_listbox.curselection()  # 获取当前选中的列表框项
if selection:  # 如果有选中的项
index = selection[0]  # 获取选中的项的索引
if index == self.current_song_index:  # 如果选中的项是当前播放的歌曲
messagebox.showwarning("警告", "当前正在播放的歌曲不能被移除。")  # 弹出警告框
return  # 结束方法执行
removed_song = self.music_files.pop(index)  # 从音乐文件列表中移除选中的文件,并返回移除的文件路径
self.song_lengths.pop(index)  # 从歌曲长度列表中移除对应长度
self.music_listbox.delete(index)  # 从列表框中删除选中的项
if self.current_song_index >= index:  # 如果当前播放歌曲的索引大于等于移除的歌曲索引
self.current_song_index = max(0, self.current_song_index - 1)  # 更新当前播放歌曲的索引为上一首歌曲
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}" if self.music_files else "当前播放: ")  # 更新当前播放歌曲的标签
messagebox.showinfo("移除音频", f"已移除选择的音频: {os.path.basename(removed_song)}")  # 弹出信息框,显示已移除的音频文件名
def on_select(self, event):  # 处理列表框选中项更改的方法
# 处理列表框选中项的更改
selection = self.music_listbox.curselection()  # 获取当前选中的列表框项
if selection:  # 如果有选中的项
self.current_song_index = selection[0]  # 更新当前播放歌曲的索引为选中的索引
self.play_music()  # 播放选中的歌曲
def play_music(self):  # 播放音乐的方法
if self.music_files:  # 如果音乐文件列表不为空
pygame.mixer.music.load(self.music_files[self.current_song_index])  # 使用全路径加载音乐文件
pygame.mixer.music.play()  # 播放音乐
self.is_playing = True  # 设置当前正在播放音乐的标志为True
self.play_button.config(text="暂停")  # 更新播放按钮的文本为“暂停”
self.current_song_label.config(
text=f"当前播放: {os.path.basename(self.music_files[self.current_song_index])}")  # 更新当前播放歌曲的标签
self.update_progress_bar_max()  # 更新进度条的最大值
self.update_progress_bar()  # 更新进度条
def toggle_play(self):  # 切换播放/暂停的方法
if self.is_playing:  # 如果当前正在播放音乐
pygame.mixer.music.pause()  # 暂停音乐
self.is_playing = False  # 设置当前正在播放音乐的标志为False
self.play_button.config(text="播放")  # 更新播放按钮的文本为“播放”
self.root.after_cancel(self.update_timer)  # 取消定时器
else:
pygame.mixer.music.unpause()  # 恢复播放音乐
self.is_playing = True  # 设置当前正在播放音乐的标志为True
self.play_button.config(text="暂停")  # 更新播放按钮的文本为“暂停”
self.update_progress_bar()  # 恢复进度条更新
def play_next_song(self):  # 播放下一首歌曲的方法
if self.music_files:  # 确保列表不为空
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index  # 不改变当前播放歌曲的索引
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)  # 随机选择一个歌曲索引
else:
self.current_song_index = (self.current_song_index + 1) % len(self.music_files)  # 播放下一首歌曲,循环播放
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.play_music()  # 播放选中的歌曲
def play_prev_song(self):  # 播放上一首歌曲的方法
if self.music_files:  # 确保列表不为空
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index  # 不改变当前播放歌曲的索引
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)  # 随机选择一个歌曲索引
else:
self.current_song_index = (self.current_song_index - 1) % len(self.music_files)  # 播放上一首歌曲,循环播放
self.music_listbox.select_set(self.current_song_index)  # 更新列表框当前选中项
self.play_music()  # 播放选中的歌曲
def toggle_play_mode(self):  # 切换播放模式的方法
if self.play_mode == 0:  # 如果当前播放模式为单曲循环
self.play_mode = 1  # 切换到随机播放模式
self.mode_button.config(text="随机播放")  # 更新播放模式切换按钮的文本为“随机播放”
else:
self.play_mode = 0  # 切换到单曲循环模式
self.mode_button.config(text="单曲循环")  # 更新播放模式切换按钮的文本为“单曲循环”
def set_volume(self, volume):  # 设置音量的方法
pygame.mixer.music.set_volume(float(volume))  # 将音量设置为滑动条的值
def update_progress_bar(self):  # 更新进度条的方法
if self.is_playing:  # 如果当前正在播放音乐
current_pos = pygame.mixer.music.get_pos() / 1000.0  # 获取当前播放位置(秒)
self.progress_scale.set(current_pos)  # 设置进度条的位置
# 更新显示的时间为分钟
total_length = self.song_lengths[self.current_song_index]  # 获取歌曲总长度(秒)
minutes_elapsed = int(current_pos // 60)  # 计算已播放的分钟数
seconds_elapsed = int(current_pos % 60)  # 计算已播放的秒数
minutes_total = int(total_length // 60)  # 计算歌曲总长度的分钟数
seconds_total = int(total_length % 60)  # 计算歌曲总长度的秒数
self.progress_time_label.config(
text=f"{minutes_elapsed}:{seconds_elapsed:02}/{minutes_total}:{seconds_total:02}")  # 格式化显示时间
self.update_timer = self.root.after(100, self.update_progress_bar)  # 每100毫秒更新一次进度条
def update_progress_bar_max(self):  # 更新进度条最大值的方法
if self.music_files:  # 如果音乐文件列表不为空
total_length = self.song_lengths[self.current_song_index]  # 获取歌曲总长度(秒)
self.progress_scale.config(to=total_length)  # 设置进度条的最大值为歌曲总长度
def seek_music(self, event):  # 跳转播放位置的方法
position = self.progress_scale.get()  # 获取当前进度条的位置
pygame.mixer.music.set_pos(position)  # 设置音乐播放位置为进度条的位置
def handle_music_end(self):  # 处理音乐播放结束的方法
if self.play_mode == 0:  # 单曲循环
self.current_song_index = self.current_song_index  # 重新播放当前歌曲
self.play_music()  # 播放歌曲
elif self.play_mode == 1:  # 随机播放
self.current_song_index = random.randint(0, len(self.music_files) - 1)  # 随机选择一首歌曲
self.play_music()  # 播放歌曲
else:
self.current_song_index = (self.current_song_index + 1) % len(self.music_files)  # 播放下一首歌曲,循环播放
self.play_music()  # 播放歌曲
def update(self):  # 检测事件并处理的方法
for event in pygame.event.get():  # 获取pygame的所有事件
if event.type == pygame.USEREVENT:  # 检测音乐结束事件
self.handle_music_end()  # 处理音乐结束事件
self.root.after(100, self.update)  # 每100毫秒检查一次事件
if __name__ == "__main__":  # 如果作为主程序运行
root = tk.Tk()  # 创建tkinter主窗口
player = MusicPlayer(root)  # 创建MusicPlayer对象
root.after(100, player.update)  # 启动事件循环检查
root.mainloop()  # 进入tkinter的主循环

 


相关文章

父亲今天已经出院了
FreeControl手机控制工具推荐
Tuboshu桌面应用转换工具
U盘引导盘制作Rufus
Scratch图形化编程少儿编程工具软件
Windows环境中使用 ServKit(PHPnow) 搭建 PHP 环境-绿色 PHP 套件

发布评论

用Python加ai写了一个本地音乐播放器