diff --git a/README.md b/README.md index bcdce8c..d6bd790 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ pip 安装依赖后即可直接播放视频。 ## 功能特性 - 播放 / 暂停 / 停止、上一首 / 下一首 -- 前进 / 后退(10 秒或 60 秒)与进度条点击拖动定位 +- 前进 / 后退(步长可自定义)与进度条点击拖动定位 +- 快进步长可在界面底部直接填写(1-600 秒),自动保存、下次启动自动加载 - 音量调节、静音,0.5x - 2.0x 变速播放 - 三种循环模式:关 / 单曲循环 / 列表循环 - 播放列表管理(打开文件、打开文件夹、删除条目、双击播放) @@ -85,6 +86,10 @@ pip install -r requirements.txt 播放列表保存在用户主目录的 `.mediaplayer_playlist.json` 中,删除该文件即可 清除记忆。 +界面底部的“快进步长”用于设置 `←` / `→` 每次跳转的秒数(`Shift+← / →` 固定为 +60 秒),输入后按回车或点击其他位置即生效并自动保存(配置文件:用户主目录下的 +`.mediaplayer_settings.json`)。 + ## 常见问题 - **状态栏显示“纯 Python 解码内核”**:说明未检测到 VLC,程序正在使用内置的 diff --git a/media_player.py b/media_player.py index d4aaab2..8be8cf9 100644 --- a/media_player.py +++ b/media_player.py @@ -830,6 +830,7 @@ class MediaPlayerApp(tk.Tk): self.current_index = -1 self.volume = 80 self.rate = 1.0 + self.seek_step = self._load_settings() self.loop_mode = "list" self._seeking = False self._switching_until = 0.0 @@ -951,19 +952,39 @@ class MediaPlayerApp(tk.Tk): self._playlist_menu.add_separator() self._playlist_menu.add_command(label="删除选中曲目", command=self.remove_selected) self._playlist_menu.add_command(label="清空播放列表", command=self.clear_playlist) - self.listbox.bind("", self._show_playlist_menu) if IS_MAC: + self.listbox.bind("", self._show_playlist_menu) + self.listbox.bind("", self._show_playlist_menu) self.listbox.bind("", self._show_playlist_menu) + else: + self.listbox.bind("", self._show_playlist_menu) bottom = ttk.Frame(self, padding=(8, 6)) bottom.pack(fill="x") self.seekbar = SeekBar(bottom, command=self._on_seek_command) self.seekbar.pack(fill="x") + controls = ttk.Frame(bottom) + controls.pack(fill="x", pady=(4, 0)) self.time_var = tk.StringVar(value="00:00 / --:--") self.status_var = tk.StringVar(value="就绪 — 打开文件或按 Ctrl+O") - ttk.Label(bottom, textvariable=self.time_var).pack(side="left", anchor="w") - ttk.Label(bottom, textvariable=self.status_var, anchor="e").pack(side="right", fill="x", expand=True) + self.seek_step_var = tk.StringVar(value=str(self.seek_step)) + ttk.Label(controls, textvariable=self.time_var).pack(side="left", anchor="w") + ttk.Label(controls, text="快进步长:").pack(side="left", padx=(16, 4)) + self.seek_step_spin = ttk.Spinbox( + controls, + from_=1, + to=600, + increment=1, + width=5, + textvariable=self.seek_step_var, + command=self._commit_seek_step, + ) + self.seek_step_spin.pack(side="left") + ttk.Label(controls, text="秒").pack(side="left", padx=(4, 0)) + self.seek_step_spin.bind("", self._on_seek_step_return) + self.seek_step_spin.bind("", lambda _e: self._commit_seek_step()) + ttk.Label(controls, textvariable=self.status_var, anchor="e").pack(side="right", fill="x", expand=True) def _bind_shortcuts(self): shortcuts = [ @@ -1012,11 +1033,11 @@ class MediaPlayerApp(tk.Tk): return "break" def _on_left(self, _event): - self.seek_relative(-SEEK_STEP) + self.seek_relative(-self.seek_step) return "break" def _on_right(self, _event): - self.seek_relative(SEEK_STEP) + self.seek_relative(self.seek_step) return "break" def _on_up(self, _event): @@ -1115,6 +1136,26 @@ class MediaPlayerApp(tk.Tk): def _playlist_path(self): return Path.home() / ".mediaplayer_playlist.json" + def _settings_path(self): + return Path.home() / ".mediaplayer_settings.json" + + def _load_settings(self): + try: + data = json.loads(self._settings_path().read_text(encoding="utf-8")) + value = int(data.get("seek_step", SEEK_STEP)) + except Exception: + value = SEEK_STEP + return value if 1 <= value <= 600 else SEEK_STEP + + def _save_settings(self): + try: + self._settings_path().write_text( + json.dumps({"seek_step": self.seek_step}, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + except Exception as exc: + print("保存设置失败:", exc) + def _save_playlist(self): try: data = {"playlist": list(self.playlist), "current_index": self.current_index} @@ -1225,6 +1266,26 @@ class MediaPlayerApp(tk.Tk): return self.seek_to(pos + seconds * 1000) + def _commit_seek_step(self): + raw = self.seek_step_var.get().strip() + try: + value = int(float(raw)) + except ValueError: + value = 0 + if not 1 <= value <= 600: + self.seek_step_var.set(str(self.seek_step)) + self.status_var.set("快进步长需为 1-600 之间的整数") + return + if value != self.seek_step: + self.seek_step = value + self._save_settings() + self.status_var.set(f"快进步长已设为 {value} 秒(已保存)") + + def _on_seek_step_return(self, _event): + self._commit_seek_step() + self.focus_set() + return "break" + def seek_to(self, ms): ms = max(0, int(ms)) if not self.engine.seek(ms):