Compare commits
No commits in common. 'master' and 'main' have entirely different histories.
@ -1,3 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/音乐播放器项目.iml" filepath="$PROJECT_DIR$/.idea/音乐播放器项目.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 雷电影
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,36 +0,0 @@
|
||||
# MusicPlayer
|
||||
|
||||
#### Description
|
||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
@ -1,46 +0,0 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
### log ###
|
||||
*.log
|
||||
|
||||
### db ###
|
||||
*.db
|
||||
*.sqlite
|
||||
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,136 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.musicplayer</groupId>
|
||||
<artifactId>musicplayer</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<javafx.version>21.0.2</javafx.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFX -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SQLite -->
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.45.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jaudiotagger for reading audio metadata -->
|
||||
<dependency>
|
||||
<groupId>org</groupId>
|
||||
<artifactId>jaudiotagger</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他可能需要的依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.14.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志系统配置 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.4.14</version>
|
||||
</dependency>
|
||||
<!-- Jackson Core for JSON processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.2</version> <!-- 使用最新稳定版 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 确保Java Stream API可用 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20231013</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.17.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson for JSON processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache HttpClient (optional, if you need more HTTP features) -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>com.musicPlayer.MusicPlayerApp</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -1,82 +0,0 @@
|
||||
//package com.musicPlayer;
|
||||
//
|
||||
//import com.musicPlayer.controller.MusicPlayerController; // You might need to get controller instance
|
||||
//import com.musicPlayer.util.FFmpegUtil;
|
||||
//import javafx.application.Application;
|
||||
//import javafx.application.Platform;
|
||||
//import javafx.fxml.FXMLLoader;
|
||||
//import javafx.scene.Parent;
|
||||
//import javafx.scene.Scene;
|
||||
//import javafx.stage.Stage;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//import java.util.Objects;
|
||||
//
|
||||
//public class MusicPlayerApp extends Application {
|
||||
//
|
||||
// private MusicPlayerController controller; // To call cleanup
|
||||
//
|
||||
// @Override
|
||||
// public void start(Stage primaryStage) throws Exception {
|
||||
// // Test environment (consider doing this more gracefully or on demand)
|
||||
// testEnvironment();
|
||||
//
|
||||
// // Load main interface
|
||||
// FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("/fxml/MusicPlayer.fxml")));
|
||||
// Parent root = loader.load();
|
||||
// controller = loader.getController(); // Get controller instance
|
||||
//
|
||||
// primaryStage.setTitle("Music Player");
|
||||
// primaryStage.setScene(new Scene(root, 800, 600));
|
||||
// primaryStage.setOnCloseRequest(event -> {
|
||||
// if (controller != null) {
|
||||
// controller.cleanupPlayerBeforeExit();
|
||||
// }
|
||||
// Platform.exit(); // Ensure JavaFX toolkit terminates
|
||||
// System.exit(0); // Force exit if needed
|
||||
// });
|
||||
// primaryStage.show();
|
||||
// }
|
||||
//
|
||||
// private void testEnvironment() {
|
||||
// // Test FFmpeg (this is a basic test, actual config status is in FFmpegUtil)
|
||||
// // String ffmpegStatus = FFmpegUtil.getConfigStatus();
|
||||
// // System.out.println("FFmpeg Status: " + ffmpegStatus);
|
||||
// // Consider logging this instead of stdout or showing in an "About" or "Diagnostics" section.
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void stop() throws Exception {
|
||||
// // This method is called when the application is shutting down.
|
||||
// if (controller != null) {
|
||||
// controller.cleanupPlayerBeforeExit();
|
||||
// }
|
||||
// System.out.println("MusicPlayerApp stop method called. Application is closing.");
|
||||
// super.stop(); // Important to call super
|
||||
// }
|
||||
//
|
||||
// public static void main(String[] args) {
|
||||
// launch(args);
|
||||
// }
|
||||
//}
|
||||
package com.musicPlayer;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class MusicPlayerApp extends Application {
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/fxml/LoginView.fxml"));
|
||||
primaryStage.setTitle("音乐播放器 - 登录");
|
||||
primaryStage.setScene(new Scene(root, 400, 350));
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
import com.musicPlayer.services.UserService;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class LoginController {
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private Button loginBtn;
|
||||
@FXML private Hyperlink toRegisterLink;
|
||||
|
||||
private final UserService userService = new UserService();
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
// 事件绑定由FXML onAction保证
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleLogin(ActionEvent event) {
|
||||
String username = usernameField.getText();
|
||||
String password = passwordField.getText();
|
||||
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
showAlert("请输入用户名和密码");
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
String jsonInputString = String.format("{\"name\":\"%s\",\"password\":\"%s\"}", username, password);
|
||||
|
||||
try {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8081/users/login"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonInputString))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200 && "登录成功".equals(response.body())) {
|
||||
// 登录成功,跳转主界面
|
||||
Stage stage = (Stage) loginBtn.getScene().getWindow();
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/fxml/MusicPlayer.fxml"));
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("/styles/main.css").toExternalForm());
|
||||
stage.setScene(scene);
|
||||
} else {
|
||||
showAlert("登录失败:" + response.body());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showAlert("网络错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlert(String msg) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("提示");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
@FXML
|
||||
private void handleToRegister(ActionEvent event) {
|
||||
try {
|
||||
Stage stage = (Stage) loginBtn.getScene().getWindow();
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/fxml/RegisterView.fxml"));
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("/styles/main.css").toExternalForm());
|
||||
stage.setScene(scene);
|
||||
} catch (Exception e) {
|
||||
showAlert("无法加载注册界面: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
public class PlaylistController {
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
import com.musicPlayer.services.UserService;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class RegisterController {
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private PasswordField confirmPasswordField;
|
||||
@FXML private Button registerBtn;
|
||||
@FXML private Hyperlink toLoginLink;
|
||||
|
||||
private final UserService userService = new UserService();
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
// 事件绑定由FXML onAction保证
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleRegister(ActionEvent event) {
|
||||
String username = usernameField.getText();
|
||||
String password = passwordField.getText();
|
||||
String confirmPassword = confirmPasswordField.getText();
|
||||
|
||||
if (username.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
|
||||
showAlert("请填写所有字段");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.equals(confirmPassword)) {
|
||||
showAlert("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
String jsonInputString = String.format("{\"name\":\"%s\",\"password\":\"%s\"}", username, password);
|
||||
|
||||
try {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8081/users/register"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonInputString))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200 && "success".equals(response.body())) {
|
||||
showAlert("注册成功,请登录!");
|
||||
handleToLogin(event);
|
||||
} else {
|
||||
showAlert(response.body());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showAlert("网络错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleToLogin(ActionEvent event) {
|
||||
try {
|
||||
Stage stage = (Stage) registerBtn.getScene().getWindow();
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/fxml/LoginView.fxml"));
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("/styles/main.css").toExternalForm());
|
||||
stage.setScene(scene);
|
||||
} catch (Exception e) {
|
||||
showAlert("无法加载登录界面: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlert(String msg) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("提示");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
public class SearchController {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
public class SettingsController {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.controller;
|
||||
|
||||
public class TimerController {
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.musicPlayer.model;
|
||||
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class LyricLine {
|
||||
private final Duration timestamp; // 歌词开始显示的时间戳
|
||||
private final String text; // 歌词文本
|
||||
|
||||
public LyricLine(Duration timestamp, String text) {
|
||||
this.timestamp = timestamp;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Duration getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + timestamp + "] " + text;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.model;
|
||||
|
||||
public class Playlist {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.model;
|
||||
|
||||
public class Settings {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.model;
|
||||
|
||||
public class User {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
public class LoginService {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
public class PlaylistService {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
public class SearchService {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
public class SettingsService {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
public class TimerService {
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
package com.musicPlayer.services;
|
||||
|
||||
import com.musicPlayer.model.User;
|
||||
import java.sql.*;
|
||||
|
||||
public class UserService {
|
||||
private static final String DB_URL = "jdbc:sqlite:musicplayer.db";
|
||||
|
||||
public UserService() {
|
||||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||||
Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL)");
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean register(String username, String password) {
|
||||
String sql = "INSERT INTO user (username, password) VALUES (?, ?)";
|
||||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
pstmt.setString(1, username);
|
||||
pstmt.setString(2, password);
|
||||
pstmt.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean login(String username, String password) {
|
||||
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
|
||||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
pstmt.setString(1, username);
|
||||
pstmt.setString(2, password);
|
||||
ResultSet rs = pstmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean userExists(String username) {
|
||||
String sql = "SELECT * FROM user WHERE username = ?";
|
||||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
pstmt.setString(1, username);
|
||||
ResultSet rs = pstmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package com.musicPlayer.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class ConfigUtil {
|
||||
private static final Properties properties = new Properties();
|
||||
|
||||
static {
|
||||
try (InputStream input = ConfigUtil.class.getClassLoader().getResourceAsStream("config.properties")) {
|
||||
if (input != null) {
|
||||
properties.load(input);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("无法加载配置文件: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String get(String key) {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
}
|
||||
@ -1,240 +0,0 @@
|
||||
package com.musicPlayer.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException; // Required for StringReader
|
||||
import java.io.StringReader; // Required for parsing lyrics from DB
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger; // Added for logging
|
||||
import org.slf4j.LoggerFactory; // Added for logging
|
||||
|
||||
import com.musicPlayer.model.LyricLine; // Required for convertLyricLinesToString
|
||||
import com.musicPlayer.model.Song;
|
||||
|
||||
public class DBUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DBUtil.class); // Added logger
|
||||
private static final String DB_URL = "jdbc:sqlite:musicplayer.db";
|
||||
private static final String INIT_SCRIPT_PATH = "src/main/resources/db/init.sql"; // Make sure this path is correct
|
||||
|
||||
public static Connection getConnection() throws SQLException {
|
||||
// No need to call initializeDatabase() on every connection request.
|
||||
// It should be called once at application startup or when DBUtil is first instantiated.
|
||||
// For simplicity here, we'll keep it, but in a larger app, manage initialization separately.
|
||||
initializeDatabase();
|
||||
return DriverManager.getConnection(DB_URL);
|
||||
}
|
||||
|
||||
private static synchronized void initializeDatabase() { // Added synchronized
|
||||
File dbFile = new File("musicplayer.db");
|
||||
if (!dbFile.exists()) {
|
||||
logger.info("Database file not found. Initializing database...");
|
||||
try {
|
||||
// Ensure the resource path is correct, especially if running from a JAR vs IDE
|
||||
String sqlScriptPath = INIT_SCRIPT_PATH;
|
||||
java.net.URL scriptUrl = DBUtil.class.getClassLoader().getResource("db/init.sql");
|
||||
|
||||
String sql;
|
||||
if (scriptUrl != null) {
|
||||
sql = new String(Files.readAllBytes(Paths.get(scriptUrl.toURI())), StandardCharsets.UTF_8);
|
||||
} else if (Files.exists(Paths.get(sqlScriptPath))) { // Fallback for direct file system access (IDE context)
|
||||
sql = new String(Files.readAllBytes(Paths.get(sqlScriptPath)), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
logger.error("init.sql script not found at {} or via ClassLoader.", sqlScriptPath);
|
||||
throw new IOException("Database initialization script not found.");
|
||||
}
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||||
Statement stmt = conn.createStatement()) {
|
||||
// SQLite JDBC driver creates the file if it doesn't exist on first connection.
|
||||
// The executeUpdate will then run the script.
|
||||
String[] statements = sql.split(";"); // Split script into individual statements
|
||||
for (String s : statements) {
|
||||
if (!s.trim().isEmpty()) {
|
||||
stmt.executeUpdate(s);
|
||||
}
|
||||
}
|
||||
logger.info("✅ 数据库初始化完成 (Database initialized successfully)");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("❌ 数据库初始化失败 (Database initialization failed): {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
// logger.debug("Database file already exists."); // Optional: log if DB already exists
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to convert List<LyricLine> to a single String for DB storage
|
||||
private String convertLyricLinesToString(List<LyricLine> lyricLines) {
|
||||
if (lyricLines == null || lyricLines.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (LyricLine line : lyricLines) {
|
||||
long totalMillis = (long) line.getTimestamp().toMillis();
|
||||
long minutes = totalMillis / 60000;
|
||||
long seconds = (totalMillis % 60000) / 1000;
|
||||
long millis = totalMillis % 1000;
|
||||
// Format: [mm:ss.xx] for centiseconds or [mm:ss.xxx] for milliseconds.
|
||||
// LrcParser handles both [mm:ss.xx] and [mm:ss.xxx].
|
||||
// Let's use centiseconds (2 digits for fractional part) as it's common.
|
||||
String timeStr = String.format("[%02d:%02d.%02d]", minutes, seconds, millis / 10);
|
||||
sb.append(timeStr).append(line.getText()).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void saveSong(Song song) {
|
||||
String lyricsForDb = convertLyricLinesToString(song.getLyrics());
|
||||
String sql = "INSERT INTO songs(title, artist, album, file_path, duration, album_art_data, lyrics_content) VALUES(?,?,?,?,?,?,?)";
|
||||
|
||||
try (Connection conn = getConnection(); // Ensure DB is initialized
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
pstmt.setString(1, song.getTitle());
|
||||
pstmt.setString(2, song.getArtist());
|
||||
pstmt.setString(3, song.getAlbum());
|
||||
pstmt.setString(4, song.getFilePath());
|
||||
pstmt.setInt(5, song.getDuration());
|
||||
pstmt.setBytes(6, song.getAlbumArtData()); // Save album art
|
||||
pstmt.setString(7, lyricsForDb); // Save lyrics content
|
||||
|
||||
pstmt.executeUpdate();
|
||||
|
||||
try (ResultSet rs = pstmt.getGeneratedKeys()) {
|
||||
if (rs.next()) {
|
||||
song.setId(rs.getInt(1));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error saving song to DB: {}", song.getTitle(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSong(Song song) {
|
||||
String lyricsForDb = convertLyricLinesToString(song.getLyrics());
|
||||
String sql = "UPDATE songs SET title = ?, artist = ?, album = ?, duration = ?, album_art_data = ?, lyrics_content = ? WHERE id = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
pstmt.setString(1, song.getTitle());
|
||||
pstmt.setString(2, song.getArtist());
|
||||
pstmt.setString(3, song.getAlbum());
|
||||
pstmt.setInt(4, song.getDuration());
|
||||
pstmt.setBytes(5, song.getAlbumArtData());
|
||||
pstmt.setString(6, lyricsForDb);
|
||||
pstmt.setInt(7, song.getId());
|
||||
pstmt.executeUpdate();
|
||||
logger.info("Updated song in DB: {}", song.getTitle());
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error updating song in DB: {}", song.getTitle(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<Song> getAllSongs() {
|
||||
List<Song> songs = new ArrayList<>();
|
||||
String sql = "SELECT * FROM songs ORDER BY title";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
Statement stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(sql)) {
|
||||
|
||||
while (rs.next()) {
|
||||
Song song = new Song(
|
||||
rs.getString("title"),
|
||||
rs.getString("artist"),
|
||||
rs.getString("album"),
|
||||
rs.getString("file_path"),
|
||||
rs.getInt("duration")
|
||||
);
|
||||
song.setId(rs.getInt("id"));
|
||||
song.setAlbumArtData(rs.getBytes("album_art_data")); // Load album art
|
||||
|
||||
String lyricsContentFromDb = rs.getString("lyrics_content");
|
||||
if (lyricsContentFromDb != null && !lyricsContentFromDb.isEmpty()) {
|
||||
try (StringReader sr = new StringReader(lyricsContentFromDb)) {
|
||||
song.setLyrics(LrcParser.parse(sr)); // Parse lyrics
|
||||
} catch (IOException ioe) {
|
||||
logger.error("Failed to parse lyrics from DB for song: {}", song.getTitle(), ioe);
|
||||
song.setLyrics(new ArrayList<>()); // Default to empty list on parsing error
|
||||
}
|
||||
} else {
|
||||
song.setLyrics(new ArrayList<>()); // Default to empty list if no lyrics in DB
|
||||
}
|
||||
songs.add(song);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error retrieving all songs from DB", e);
|
||||
}
|
||||
return songs;
|
||||
}
|
||||
|
||||
public Song findSongByPath(String filePath) {
|
||||
String sql = "SELECT * FROM songs WHERE file_path = ?";
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
pstmt.setString(1, filePath);
|
||||
|
||||
try (ResultSet rs = pstmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
Song song = new Song(
|
||||
rs.getString("title"),
|
||||
rs.getString("artist"),
|
||||
rs.getString("album"),
|
||||
rs.getString("file_path"),
|
||||
rs.getInt("duration")
|
||||
);
|
||||
song.setId(rs.getInt("id"));
|
||||
song.setAlbumArtData(rs.getBytes("album_art_data")); // Load album art
|
||||
|
||||
String lyricsContentFromDb = rs.getString("lyrics_content");
|
||||
if (lyricsContentFromDb != null && !lyricsContentFromDb.isEmpty()) {
|
||||
try (StringReader sr = new StringReader(lyricsContentFromDb)) {
|
||||
song.setLyrics(LrcParser.parse(sr)); // Parse lyrics
|
||||
} catch (IOException ioe) {
|
||||
logger.error("Failed to parse lyrics from DB for song (by path): {}", song.getTitle(), ioe);
|
||||
song.setLyrics(new ArrayList<>());
|
||||
}
|
||||
} else {
|
||||
song.setLyrics(new ArrayList<>());
|
||||
}
|
||||
return song;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error finding song by path: {}", filePath, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void deleteSong(Song song) {
|
||||
if (song == null) return;
|
||||
// Can use ID if available and more reliable, otherwise filePath
|
||||
String sql = (song.getId() > 0) ? "DELETE FROM songs WHERE id = ?" : "DELETE FROM songs WHERE file_path = ?";
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
if (song.getId() > 0) {
|
||||
pstmt.setInt(1, song.getId());
|
||||
} else {
|
||||
pstmt.setString(1, song.getFilePath());
|
||||
}
|
||||
pstmt.executeUpdate();
|
||||
logger.info("Deleted song: {}", song.getTitle());
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error deleting song: {}", song.getTitle(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed static from addSong, songExists for consistency if DBUtil is instanced.
|
||||
// If DBUtil is purely static methods, then keep them static and ensure getConnection handles init.
|
||||
// For now, assuming it might be instanced by MusicPlayerService or used statically as is.
|
||||
// The key changes are in saveSong, getAllSongs, findSongByPath.
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
package com.musicPlayer.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader; // Added
|
||||
import java.io.StringReader; // Added for convenience if directly used
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
|
||||
import com.musicPlayer.model.LyricLine;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class LrcParser {
|
||||
|
||||
private static final Pattern timestampPattern = Pattern.compile("\\[(\\d{2}):(\\d{2})[.:](\\d{2,3})\\]");
|
||||
|
||||
public static List<LyricLine> parse(File lrcFile) throws IOException {
|
||||
List<LyricLine> lyrics = new ArrayList<>();
|
||||
if (!lrcFile.exists() || !lrcFile.isFile()) {
|
||||
// System.out.println("LRC file does not exist or is not a file: " + lrcFile.getAbsolutePath());
|
||||
return lyrics; // File doesn't exist or is not a file, return empty list
|
||||
}
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(lrcFile))) {
|
||||
return parse(reader); // Delegate to the Reader-based method
|
||||
}
|
||||
}
|
||||
|
||||
// Overloaded method to parse from any Reader
|
||||
public static List<LyricLine> parse(Reader inputReader) throws IOException {
|
||||
List<LyricLine> lyrics = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(inputReader)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
parseLine(line, lyrics);
|
||||
}
|
||||
}
|
||||
// Sort lyrics by timestamp after parsing all lines
|
||||
Collections.sort(lyrics, Comparator.comparing(LyricLine::getTimestamp));
|
||||
return lyrics;
|
||||
}
|
||||
|
||||
private static void parseLine(String line, List<LyricLine> lyrics) {
|
||||
Matcher matcher = timestampPattern.matcher(line);
|
||||
List<Duration> timestamps = new ArrayList<>();
|
||||
int lastMatchEnd = 0;
|
||||
|
||||
// Extract all timestamps from the beginning of the line
|
||||
while (matcher.find() && matcher.start() == lastMatchEnd) {
|
||||
int minutes = Integer.parseInt(matcher.group(1));
|
||||
int seconds = Integer.parseInt(matcher.group(2));
|
||||
String millisOrCentis = matcher.group(3);
|
||||
int milliseconds = 0;
|
||||
if (millisOrCentis.length() == 3) { // Milliseconds
|
||||
milliseconds = Integer.parseInt(millisOrCentis);
|
||||
} else if (millisOrCentis.length() == 2) { // Centiseconds
|
||||
milliseconds = Integer.parseInt(millisOrCentis) * 10;
|
||||
}
|
||||
|
||||
timestamps.add(Duration.seconds(minutes * 60 + seconds).add(Duration.millis(milliseconds)));
|
||||
lastMatchEnd = matcher.end();
|
||||
}
|
||||
|
||||
// The text part of the lyric is after all timestamps
|
||||
String text = line.substring(lastMatchEnd).trim();
|
||||
|
||||
// If timestamps were found, create LyricLine objects
|
||||
if (!timestamps.isEmpty()) {
|
||||
for (Duration timestamp : timestamps) {
|
||||
lyrics.add(new LyricLine(timestamp, text));
|
||||
}
|
||||
} else if (!line.trim().isEmpty() && !timestampPattern.matcher(line.trim()).find() && !isMetadataTag(line)) {
|
||||
// Handle lines that are purely text (no timestamps) - potentially add as untimed or ignore
|
||||
// For now, we only add lines that have associated timestamps as per typical LRC format
|
||||
// System.out.println("Skipping non-timestamped, non-metadata line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMetadataTag(String line) {
|
||||
// Simple check for common LRC metadata tags like [ti:], [ar:], [al:]
|
||||
return line.matches("^\\[(ti|ar|al|au|by|offset|re|ve|length):.*\\]$");
|
||||
}
|
||||
|
||||
|
||||
public static File autoGenerateLrcWithTimestamps(File mp3File, File rawLyricFile, int intervalSec) throws IOException {
|
||||
List<String> lines = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(rawLyricFile), "UTF-8"))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.trim().isEmpty()) {
|
||||
lines.add(line.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
String mp3Path = mp3File.getAbsolutePath();
|
||||
String lrcPath = mp3Path.substring(0, mp3Path.lastIndexOf('.')) + ".lrc";
|
||||
File lrcFile = new File(lrcPath);
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(lrcFile), "UTF-8"))) {
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
int totalSec = i * intervalSec;
|
||||
int min = totalSec / 60;
|
||||
int sec = totalSec % 60;
|
||||
writer.write(String.format("[%02d:%02d.00]%s", min, sec, lines.get(i)));
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
return lrcFile;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class LoginViewController {
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class MusicPlayerViewController {
|
||||
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class PlaylistViewController {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class SearchViewController {
|
||||
}
|
||||
@ -1,359 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class SettingsViewController implements Initializable {
|
||||
|
||||
// === UI Components ===
|
||||
@FXML private TableView<Shortcut> shortcutsTable;
|
||||
@FXML private TableColumn<Shortcut, String> functionColumn;
|
||||
@FXML private TableColumn<Shortcut, String> shortcutColumn;
|
||||
@FXML private CheckBox enableShortcutsCheckBox;
|
||||
@FXML private TextField shortcutInput;
|
||||
@FXML private Button confirmButton;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button applyButton;
|
||||
@FXML private Button restoreDefaultsButton;
|
||||
|
||||
private Shortcut currentlyEditing;
|
||||
|
||||
// === Data Model ===
|
||||
private final ObservableList<Shortcut> shortcutData = FXCollections.observableArrayList(
|
||||
new Shortcut("播放/暂停", "Ctrl+Shift+F5"),
|
||||
new Shortcut("停止", "Ctrl+Shift+F6"),
|
||||
new Shortcut("快进", "Ctrl+Shift+F8"),
|
||||
new Shortcut("快退", "Ctrl+Shift+F7"),
|
||||
new Shortcut("上一曲", "Ctrl+Shift+Left"),
|
||||
new Shortcut("下一曲", "Ctrl+Shift+Right"),
|
||||
new Shortcut("增大音量", "Ctrl+Shift+Up"),
|
||||
new Shortcut("减小音量", "Ctrl+Shift+Down"),
|
||||
new Shortcut("退出", ""),
|
||||
new Shortcut("显示/隐藏播放器", ""),
|
||||
new Shortcut("显示/隐藏桌面歌词", ""),
|
||||
new Shortcut("添加到我喜欢的音乐", "")
|
||||
);
|
||||
|
||||
// === Initialization ===
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
setupTableColumns();
|
||||
setupEnableCheckbox();
|
||||
loadSavedShortcuts();
|
||||
setupButtonActions();
|
||||
|
||||
// 设置行选择监听
|
||||
shortcutsTable.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
currentlyEditing = newVal;
|
||||
shortcutInput.setText(newVal.getShortcut().isEmpty() ? "" : newVal.getShortcut());
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框按键监听
|
||||
shortcutInput.setOnKeyPressed(this::captureShortcut);
|
||||
}
|
||||
|
||||
private void setupTableColumns() {
|
||||
functionColumn.setCellValueFactory(new PropertyValueFactory<>("function"));
|
||||
shortcutColumn.setCellValueFactory(new PropertyValueFactory<>("formattedShortcut"));
|
||||
shortcutColumn.setCellFactory(param -> new ShortcutCell());
|
||||
|
||||
// 设置列宽比例
|
||||
functionColumn.setPrefWidth(380);
|
||||
shortcutColumn.setPrefWidth(300);
|
||||
}
|
||||
|
||||
private void setupEnableCheckbox() {
|
||||
enableShortcutsCheckBox.selectedProperty().addListener((obs, oldVal, enabled) -> {
|
||||
shortcutsTable.setDisable(!enabled);
|
||||
if (!enabled) {
|
||||
shortcutsTable.getSelectionModel().clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSavedShortcuts() {
|
||||
Preferences prefs = Preferences.userNodeForPackage(MusicPlayerViewController.class);
|
||||
|
||||
// 加载快捷键
|
||||
shortcutData.forEach(shortcut -> {
|
||||
String savedShortcut = prefs.get("shortcut." + shortcut.getFunction(), shortcut.getShortcut());
|
||||
shortcut.setShortcut(savedShortcut);
|
||||
});
|
||||
|
||||
// 加载启用状态
|
||||
enableShortcutsCheckBox.setSelected(prefs.getBoolean("shortcuts.enabled", true));
|
||||
|
||||
shortcutsTable.setItems(shortcutData);
|
||||
}
|
||||
|
||||
private void setupButtonActions() {
|
||||
confirmButton.setOnAction(e -> handleConfirm());
|
||||
cancelButton.setOnAction(e -> handleCancel());
|
||||
applyButton.setOnAction(e -> handleApply());
|
||||
restoreDefaultsButton.setOnAction(e -> handleRestoreDefaults());
|
||||
}
|
||||
|
||||
// === Shortcut Capture ===
|
||||
private void captureShortcut(KeyEvent event) {
|
||||
String newShortcut = buildShortcutString(event);
|
||||
shortcutInput.setText(newShortcut);
|
||||
event.consume();
|
||||
}
|
||||
|
||||
private String buildShortcutString(KeyEvent event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (event.isControlDown()) sb.append("Ctrl+");
|
||||
if (event.isShiftDown()) sb.append("Shift+");
|
||||
if (event.isAltDown()) sb.append("Alt+");
|
||||
|
||||
if (!event.getCode().isModifierKey()) {
|
||||
sb.append(event.getCode().getName());
|
||||
} else if (sb.length() > 0) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void handleShortcutUpdate(ActionEvent event) {
|
||||
if (currentlyEditing != null && !shortcutInput.getText().isEmpty()) {
|
||||
// 检查快捷键冲突
|
||||
if (isShortcutConflict(shortcutInput.getText(), currentlyEditing)) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("快捷键冲突");
|
||||
alert.setHeaderText("该快捷键已被其他功能使用");
|
||||
alert.setContentText("请选择其他快捷键组合");
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
currentlyEditing.setShortcut(shortcutInput.getText());
|
||||
shortcutsTable.refresh();
|
||||
}
|
||||
}
|
||||
private boolean isShortcutConflict(String newShortcut, Shortcut currentShortcut) {
|
||||
if (newShortcut.isEmpty()) return false;
|
||||
|
||||
return shortcutData.stream()
|
||||
.filter(s -> !s.equals(currentShortcut))
|
||||
.anyMatch(s -> s.getShortcut().equals(newShortcut));
|
||||
}
|
||||
// === Button Handlers ===
|
||||
@FXML
|
||||
private void handleConfirm() {
|
||||
saveShortcuts();
|
||||
closeWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleCancel() {
|
||||
closeWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleApply() {
|
||||
saveShortcuts();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleRestoreDefaults() {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle("恢复默认设置");
|
||||
alert.setHeaderText("确定要恢复默认快捷键吗?");
|
||||
alert.setContentText("所有自定义快捷键设置将被重置");
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
ObservableList<Shortcut> defaultData = FXCollections.observableArrayList(
|
||||
new Shortcut("播放/暂停", "Ctrl+Shift+F5"),
|
||||
new Shortcut("停止", "Ctrl+Shift+F6"),
|
||||
new Shortcut("快进", "Ctrl+Shift+F8"),
|
||||
new Shortcut("快退", "Ctrl+Shift+F7"),
|
||||
new Shortcut("上一曲", "Ctrl+Shift+Left"),
|
||||
new Shortcut("下一曲", "Ctrl+Shift+Right"),
|
||||
new Shortcut("增大音量", "Ctrl+Shift+Up"),
|
||||
new Shortcut("减小音量", "Ctrl+Shift+Down"),
|
||||
new Shortcut("退出", ""),
|
||||
new Shortcut("显示/隐藏播放器", ""),
|
||||
new Shortcut("显示/隐藏桌面歌词", ""),
|
||||
new Shortcut("添加到我喜欢的音乐", "")
|
||||
);
|
||||
|
||||
shortcutData.setAll(defaultData);
|
||||
shortcutsTable.refresh();
|
||||
enableShortcutsCheckBox.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
// === Utility Methods ===
|
||||
private void saveShortcuts() {
|
||||
Preferences prefs = Preferences.userNodeForPackage(MusicPlayerViewController.class);
|
||||
|
||||
// 保存所有快捷键
|
||||
shortcutData.forEach(shortcut ->
|
||||
prefs.put("shortcut." + shortcut.getFunction(), shortcut.getShortcut())
|
||||
);
|
||||
|
||||
// 保存启用状态
|
||||
prefs.putBoolean("shortcuts.enabled", enableShortcutsCheckBox.isSelected());
|
||||
|
||||
// 显示保存成功提示
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("保存成功");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText("快捷键设置已保存");
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void closeWindow() {
|
||||
Stage stage = (Stage) shortcutsTable.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
// === Custom Table Cell ===
|
||||
private class ShortcutCell extends TableCell<Shortcut, String> {
|
||||
private final Button setButton = new Button("设置");
|
||||
private final Label shortcutLabel = new Label();
|
||||
|
||||
public ShortcutCell() {
|
||||
setButton.setStyle("-fx-background-color: #4a90e2; -fx-text-fill: white;");
|
||||
setButton.setOnAction(e -> startKeyBinding());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || getTableRow() == null) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Shortcut shortcut = getTableRow().getItem();
|
||||
if (shortcut == null) return;
|
||||
|
||||
// 特殊处理"添加到我喜欢的音乐"行
|
||||
if ("添加到我喜欢的音乐".equals(shortcut.getFunction())) {
|
||||
HBox container = new HBox(0, new Label(""), setButton);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
setGraphic(container);
|
||||
getTableRow().setStyle("-fx-background-color: #e6f2ff;");
|
||||
}
|
||||
// 处理其他行的显示
|
||||
else if (shortcut.getShortcut().isEmpty()) {
|
||||
setGraphic(setButton);
|
||||
} else {
|
||||
shortcutLabel.setText(shortcut.getFormattedShortcut());
|
||||
setGraphic(shortcutLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void startKeyBinding() {
|
||||
setGraphic(new Label("按下按键组合..."));
|
||||
|
||||
// 临时保存原始快捷键
|
||||
Shortcut shortcut = getTableRow().getItem();
|
||||
String originalShortcut = shortcut.getShortcut();
|
||||
|
||||
// 设置按键监听
|
||||
getScene().setOnKeyPressed(event -> {
|
||||
String newShortcut = buildShortcutString(event);
|
||||
|
||||
// 检查快捷键冲突
|
||||
if (isShortcutConflict(newShortcut, shortcut)) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("快捷键冲突");
|
||||
alert.setHeaderText("该快捷键已被其他功能使用");
|
||||
alert.setContentText("请选择其他快捷键组合");
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
shortcut.setShortcut(newShortcut);
|
||||
updateItem(shortcut.getFormattedShortcut(), false);
|
||||
|
||||
// 移除监听
|
||||
getScene().setOnKeyPressed(null);
|
||||
});
|
||||
|
||||
// 点击空白处取消
|
||||
getScene().setOnMouseClicked(event -> {
|
||||
shortcut.setShortcut(originalShortcut);
|
||||
updateItem(shortcut.getFormattedShortcut(), false);
|
||||
getScene().setOnKeyPressed(null);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isShortcutConflict(String newShortcut, Shortcut currentShortcut) {
|
||||
if (newShortcut.isEmpty()) return false;
|
||||
|
||||
return shortcutData.stream()
|
||||
.filter(s -> !s.equals(currentShortcut))
|
||||
.anyMatch(s -> s.getShortcut().equals(newShortcut));
|
||||
}
|
||||
|
||||
private String buildShortcutString(KeyEvent event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (event.isControlDown()) sb.append("Ctrl+");
|
||||
if (event.isShiftDown()) sb.append("Shift+");
|
||||
if (event.isAltDown()) sb.append("Alt+");
|
||||
|
||||
if (!event.getCode().isModifierKey()) {
|
||||
sb.append(event.getCode().getName());
|
||||
} else if (sb.length() > 0) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// === Data Model Class ===
|
||||
public static class Shortcut {
|
||||
private final StringProperty function;
|
||||
private final StringProperty shortcut;
|
||||
private final StringProperty formattedShortcut;
|
||||
|
||||
public Shortcut(String function, String shortcut) {
|
||||
this.function = new SimpleStringProperty(function);
|
||||
this.shortcut = new SimpleStringProperty(shortcut);
|
||||
this.formattedShortcut = new SimpleStringProperty(
|
||||
shortcut.isEmpty() ? "" : shortcut.replace("+", " + ")
|
||||
);
|
||||
}
|
||||
|
||||
public void setShortcut(String newShortcut) {
|
||||
shortcut.set(newShortcut);
|
||||
formattedShortcut.set(
|
||||
newShortcut.isEmpty() ? "" : newShortcut.replace("+", " + ")
|
||||
);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getFunction() { return function.get(); }
|
||||
public String getShortcut() { return shortcut.get(); }
|
||||
public String getFormattedShortcut() { return formattedShortcut.get(); }
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class SocialViewController {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class TimerViewController {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.musicPlayer.viewControllers;
|
||||
|
||||
public class UserViewController {
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<VBox spacing="10" styleClass="main-container">
|
||||
<!-- 现有的播放列表 -->
|
||||
<ListView fx:id="songListView" VBox.vgrow="ALWAYS" styleClass="song-list">
|
||||
<cellFactory>
|
||||
<SongCellFactory />
|
||||
</cellFactory>
|
||||
</ListView>
|
||||
|
||||
<!-- 添加推荐列表 -->
|
||||
<Label text="推荐歌曲" styleClass="section-label"/>
|
||||
<ListView fx:id="recommendationListView" VBox.vgrow="ALWAYS" styleClass="recommendation-list">
|
||||
<cellFactory>
|
||||
<SongCellFactory />
|
||||
</cellFactory>
|
||||
</ListView>
|
||||
|
||||
<!-- 现有的控制按钮 -->
|
||||
// ... existing controls ...
|
||||
</VBox>
|
||||
@ -1,27 +0,0 @@
|
||||
.recommendation-list {
|
||||
-fx-background-color: #2a2a2a;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 5;
|
||||
}
|
||||
|
||||
.recommendation-list .list-cell {
|
||||
-fx-background-color: transparent;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-padding: 5 10;
|
||||
}
|
||||
|
||||
.recommendation-list .list-cell:selected {
|
||||
-fx-background-color: #1DB954;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.recommendation-list .list-cell:hover {
|
||||
-fx-background-color: #404040;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 5 10;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
deepseek.api.key=sk-5e865615b5d1450ea412d6a9efe961f9
|
||||
@ -1,31 +0,0 @@
|
||||
-- 歌曲表
|
||||
CREATE TABLE IF NOT EXISTS songs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
artist TEXT,
|
||||
album TEXT,
|
||||
file_path TEXT UNIQUE NOT NULL,
|
||||
duration INTEGER,
|
||||
album_art_data BLOB, -- Added for album art
|
||||
lyrics_content TEXT -- Added for raw lyrics text
|
||||
);
|
||||
|
||||
-- 播放列表表
|
||||
CREATE TABLE IF NOT EXISTS playlists (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
-- 播放列表-歌曲关联表
|
||||
CREATE TABLE IF NOT EXISTS playlist_songs (
|
||||
playlist_id INTEGER,
|
||||
song_id INTEGER,
|
||||
position INTEGER,
|
||||
PRIMARY KEY (playlist_id, song_id),
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 创建索引提升查询性能
|
||||
CREATE INDEX IF NOT EXISTS idx_song_artist ON songs(artist);
|
||||
CREATE INDEX IF NOT EXISTS idx_playlist_name ON playlists(name);
|
||||
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox alignment="CENTER" spacing="22" xmlns="http://javafx.com/javafx/21.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.musicPlayer.controller.LoginController" stylesheets="@../styles/main.css">
|
||||
<Label text="用户登录" styleClass="song-title"/>
|
||||
<TextField fx:id="usernameField" promptText="用户名" maxWidth="260" styleClass="text-field"/>
|
||||
<PasswordField fx:id="passwordField" promptText="密码" maxWidth="260" styleClass="password-field"/>
|
||||
<Button fx:id="loginBtn" text="登录" minWidth="260" styleClass="button" onAction="#handleLogin"/>
|
||||
<Hyperlink fx:id="toRegisterLink" text="没有账号?去注册" onAction="#handleToRegister" styleClass="hyperlink"/>
|
||||
</VBox>
|
||||
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox alignment="CENTER" spacing="15" xmlns="http://javafx.com/javafx/21.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.musicPlayer.controller.RegisterController" stylesheets="@../styles/main.css">
|
||||
<Label text="用户注册" styleClass="song-title"/>
|
||||
<TextField fx:id="usernameField" promptText="用户名" maxWidth="220" styleClass="text-field"/>
|
||||
<PasswordField fx:id="passwordField" promptText="密码" maxWidth="220" styleClass="text-field"/>
|
||||
<PasswordField fx:id="confirmPasswordField" promptText="确认密码" maxWidth="220" styleClass="text-field"/>
|
||||
<Button fx:id="registerBtn" text="注册" minWidth="260" styleClass="button" onAction="#handleRegister"/>
|
||||
<Hyperlink fx:id="toLoginLink" text="已有账号?去登录" onAction="#handleToLogin" styleClass="hyperlink"/>
|
||||
</VBox>
|
||||
@ -1,172 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.musicPlayer.viewControllers.SettingsViewController">
|
||||
<children>
|
||||
<TabPane layoutX="10.0" layoutY="10.0" prefHeight="600.0" prefWidth="800.0" tabClosingPolicy="UNAVAILABLE">
|
||||
<tabs>
|
||||
<Tab text="常规设置">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<Label layoutX="14.0" layoutY="14.0" text="语言 (Language):" />
|
||||
<ComboBox layoutX="153.0" layoutY="10.0" prefWidth="150.0" />
|
||||
<CheckBox layoutX="14.0" layoutY="44.0" mnemonicParsing="false" text="开机自动运行" />
|
||||
<CheckBox layoutX="14.0" layoutY="74.0" mnemonicParsing="false" text="启动时检查更新" />
|
||||
<CheckBox layoutX="14.0" layoutY="104.0" mnemonicParsing="false" text="优先使用内嵌歌词" />
|
||||
<CheckBox layoutX="14.0" layoutY="134.0" mnemonicParsing="false" text="显示歌词翻译(如果有)" />
|
||||
<CheckBox layoutX="14.0" layoutY="164.0" mnemonicParsing="false" text="歌词卡拉OK样式显示" />
|
||||
<CheckBox layoutX="14.0" layoutY="194.0" mnemonicParsing="false" text="歌词模糊匹配" />
|
||||
<CheckBox layoutX="14.0" layoutY="224.0" mnemonicParsing="false" text="没有歌词时显示歌曲信息" />
|
||||
<Button layoutX="14.0" layoutY="254.0" mnemonicParsing="false" text="浏览..." />
|
||||
<Label layoutX="14.0" layoutY="284.0" text="歌词文件夹:" />
|
||||
<TextField layoutX="153.0" layoutY="280.0" prefWidth="200.0" />
|
||||
<Label layoutX="14.0" layoutY="314.0" text="歌词调整后:" />
|
||||
<ComboBox layoutX="153.0" layoutY="310.0" prefWidth="150.0" />
|
||||
<CheckBox layoutX="14.0" layoutY="344.0" mnemonicParsing="false" text="不显示歌词空行" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="外观设置">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<Label layoutX="14.0" layoutY="14.0" text="频谱分析高度:" />
|
||||
<Slider layoutX="153.0" layoutY="10.0" prefWidth="200.0" />
|
||||
<CheckBox layoutX="14.0" layoutY="44.0" mnemonicParsing="false" text="显示专辑封面" />
|
||||
<CheckBox layoutX="14.0" layoutY="74.0" mnemonicParsing="false" text="深色模式" />
|
||||
<CheckBox layoutX="14.0" layoutY="104.0" mnemonicParsing="false" text="启用背景" />
|
||||
<CheckBox layoutX="14.0" layoutY="134.0" mnemonicParsing="false" text="使用专辑封面作为背景" />
|
||||
<TextField layoutX="153.0" layoutY="164.0" prefWidth="200.0" />
|
||||
<Label layoutX="14.0" layoutY="194.0" text="背景不透明度:" />
|
||||
<Slider layoutX="153.0" layoutY="190.0" prefWidth="200.0" />
|
||||
<Label layoutX="14.0" layoutY="224.0" text="高斯模糊半径:" />
|
||||
<Slider layoutX="153.0" layoutY="220.0" prefWidth="200.0" />
|
||||
<Label layoutX="14.0" layoutY="254.0" text="封面选项" />
|
||||
<CheckBox layoutX="14.0" layoutY="284.0" mnemonicParsing="false" text="优先使用内嵌专辑封面" />
|
||||
<CheckBox layoutX="14.0" layoutY="314.0" mnemonicParsing="false" text="没有专辑封面时使用外部图片" />
|
||||
<TextField layoutX="153.0" layoutY="284.0" prefWidth="200.0" />
|
||||
<Label layoutX="14.0" layoutY="344.0" text="主题颜色" />
|
||||
<ColorPicker layoutX="153.0" layoutY="340.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="播放设置">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<CheckBox layoutX="14.0" layoutY="14.0" mnemonicParsing="false" text="出现错误时停止播放" />
|
||||
<CheckBox layoutX="14.0" layoutY="44.0" mnemonicParsing="false" text="在任务栏显示播放进度" />
|
||||
<CheckBox layoutX="14.0" layoutY="74.0" mnemonicParsing="false" text="声音淡入淡出效果" />
|
||||
<CheckBox layoutX="14.0" layoutY="104.0" mnemonicParsing="false" text="使用系统媒体控件" />
|
||||
<Label layoutX="14.0" layoutY="134.0" text="播放内核" />
|
||||
<RadioButton layoutX="153.0" layoutY="130.0" mnemonicParsing="false" text="BASS" />
|
||||
<RadioButton layoutX="250.0" layoutY="130.0" mnemonicParsing="false" text="FFMPEG" />
|
||||
<Button layoutX="350.0" layoutY="130.0" mnemonicParsing="false" text="点击此处下载" />
|
||||
<Label layoutX="14.0" layoutY="164.0" text="播放设备" />
|
||||
<ComboBox layoutX="153.0" layoutY="160.0" prefWidth="150.0" />
|
||||
<Label layoutX="14.0" layoutY="194.0" text="FFMPEG 核心设置" />
|
||||
<Slider layoutX="153.0" layoutY="190.0" prefWidth="200.0" />
|
||||
<Label layoutX="363.0" layoutY="194.0" text="缓冲时长 (秒):" />
|
||||
<Slider layoutX="530.0" layoutY="190.0" prefWidth="200.0" />
|
||||
<Label layoutX="363.0" layoutY="224.0" text="最大重试次数 (-1为无限制):" />
|
||||
<Slider layoutX="530.0" layoutY="220.0" prefWidth="200.0" />
|
||||
<Label layoutX="363.0" layoutY="254.0" text="非本地文件重试间隔 (秒):" />
|
||||
<Slider layoutX="530.0" layoutY="250.0" prefWidth="200.0" />
|
||||
<Label layoutX="363.0" layoutY="284.0" text="定位等操作最大等待时间 (毫秒):" />
|
||||
<Slider layoutX="530.0" layoutY="280.0" prefWidth="200.0" />
|
||||
<CheckBox layoutX="14.0" layoutY="314.0" mnemonicParsing="false" text="启用WASAPI" />
|
||||
<CheckBox layoutX="14.0" layoutY="344.0" mnemonicParsing="false" text="启用独占模式" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="媒体库">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<CheckBox layoutX="14.0" layoutY="14.0" mnemonicParsing="false" text="禁用从磁盘删除" />
|
||||
<CheckBox layoutX="14.0" layoutY="44.0" mnemonicParsing="false" text="将只有一项的分类归到其他类中" />
|
||||
<Label layoutX="14.0" layoutY="74.0" text="音频文件低时长阈值 (秒):" />
|
||||
<Slider layoutX="153.0" layoutY="70.0" prefWidth="200.0" />
|
||||
<Label layoutX="363.0" layoutY="74.0" text="艺术家识别例外:" />
|
||||
<TextField layoutX="530.0" layoutY="70.0" prefWidth="200.0" />
|
||||
<CheckBox layoutX="14.0" layoutY="104.0" mnemonicParsing="false" text="移除不存在的音频文件" />
|
||||
<CheckBox layoutX="14.0" layoutY="134.0" mnemonicParsing="false" text="忽略时长低于阈值的文件" />
|
||||
<Button layoutX="530.0" layoutY="130.0" mnemonicParsing="false" text="强制重新加载" />
|
||||
<Label layoutX="14.0" layoutY="164.0" text="媒体库目录" />
|
||||
<TextField layoutX="153.0" layoutY="160.0" prefWidth="400.0" />
|
||||
<Button layoutX="563.0" layoutY="160.0" mnemonicParsing="false" text="添加..." />
|
||||
<Button layoutX="630.0" layoutY="160.0" mnemonicParsing="false" text="删除" />
|
||||
<CheckBox layoutX="14.0" layoutY="194.0" mnemonicParsing="false" text="启动时自动更新媒体库" />
|
||||
<CheckBox layoutX="14.0" layoutY="224.0" mnemonicParsing="false" text="在媒体库中显示的项目" />
|
||||
<CheckBox layoutX="153.0" layoutY="224.0" mnemonicParsing="false" text="艺术家" />
|
||||
<CheckBox layoutX="250.0" layoutY="224.0" mnemonicParsing="false" text="流派" />
|
||||
<CheckBox layoutX="153.0" layoutY="254.0" mnemonicParsing="false" text="唱片集" />
|
||||
<CheckBox layoutX="250.0" layoutY="254.0" mnemonicParsing="false" text="年份" />
|
||||
<CheckBox layoutX="153.0" layoutY="284.0" mnemonicParsing="false" text="文件类型" />
|
||||
<CheckBox layoutX="250.0" layoutY="284.0" mnemonicParsing="false" text="比特率" />
|
||||
<CheckBox layoutX="153.0" layoutY="314.0" mnemonicParsing="false" text="分级" />
|
||||
<CheckBox layoutX="250.0" layoutY="314.0" mnemonicParsing="false" text="所有曲目" />
|
||||
<CheckBox layoutX="153.0" layoutY="344.0" mnemonicParsing="false" text="最近播放" />
|
||||
<CheckBox layoutX="250.0" layoutY="344.0" mnemonicParsing="false" text="文件夹浏览" />
|
||||
<CheckBox layoutX="14.0" layoutY="374.0" mnemonicParsing="false" text="播放列表选项" />
|
||||
<CheckBox layoutX="153.0" layoutY="374.0" mnemonicParsing="false" text="禁用播放列表曲目拖放排序" />
|
||||
<CheckBox layoutX="250.0" layoutY="374.0" mnemonicParsing="false" text="向播放列表中添加曲目时插入到开头而不是末尾" />
|
||||
<CheckBox layoutX="14.0" layoutY="404.0" mnemonicParsing="false" text="默认使用浮动播放列表" />
|
||||
<CheckBox layoutX="153.0" layoutY="404.0" mnemonicParsing="false" text="浮动播放列表跟随主窗口" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="全局快捷键">
|
||||
<content>
|
||||
<AnchorPane style="-fx-background-color: #f0f0f0;">
|
||||
<children>
|
||||
<!-- 顶部复选框 -->
|
||||
<CheckBox fx:id="enableShortcutsCheckBox" layoutX="14.0" layoutY="14.0"
|
||||
selected="true" text="启用全局快捷键"/>
|
||||
|
||||
<!-- 快捷键表格 -->
|
||||
<TableView fx:id="shortcutsTable" layoutX="14.0" layoutY="44.0"
|
||||
prefHeight="450.0" prefWidth="780.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="functionColumn" prefWidth="380.0" text="功能"/>
|
||||
<TableColumn fx:id="shortcutColumn" prefWidth="300.0" text="快捷键"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
<!-- 新增的快捷键输入区域 -->
|
||||
<HBox layoutX="14.0" layoutY="510.0" spacing="10" style="-fx-background-color: white; -fx-padding: 10;">
|
||||
<Label text="修改快捷键:"/>
|
||||
<TextField fx:id="shortcutInput" prefWidth="300.0" promptText="请按下快捷键组合"/>
|
||||
<Button text="确认修改" onAction="#handleShortcutUpdate"
|
||||
style="-fx-background-color: #4a90e2; -fx-text-fill: white;"/>
|
||||
</HBox>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<HBox layoutX="540.0" layoutY="505.0" spacing="10">
|
||||
<!-- <Button text="确定" onAction="#handleConfirm" style="-fx-min-width: 80;"/>-->
|
||||
<!-- <Button text="取消" onAction="#handleCancel" style="-fx-min-width: 80;"/>-->
|
||||
<!-- <Button text="应用" onAction="#handleApply" style="-fx-min-width: 80;"/>-->
|
||||
<Button fx:id="restoreDefaultsButton" text="恢复默认" onAction="#handleRestoreDefaults" style="-fx-min-width: 80;"/>
|
||||
<Button fx:id="applyButton" text="应用" onAction="#handleApply" style="-fx-min-width: 80;"/>
|
||||
<!-- <Button fx:id="cancelButton" text="取消" onAction="#handleCancel" style="-fx-min-width: 80;"/>-->
|
||||
<!-- <Button fx:id="confirmButton" text="确定" onAction="#handleConfirm" style="-fx-min-width: 80;"/>-->
|
||||
</HBox>
|
||||
<HBox layoutX="540.0" layoutY="535.0" spacing="10">
|
||||
<Button fx:id="cancelButton" text="取消" onAction="#handleCancel" style="-fx-min-width: 80;"/>
|
||||
<Button fx:id="confirmButton" text="确定" onAction="#handleConfirm" style="-fx-min-width: 80;"/>
|
||||
</HBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="30 seconds">
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件日志 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/musicplayer.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/musicplayer.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 日志级别设置 -->
|
||||
<logger name="com.musicPlayer" level="DEBUG"/>
|
||||
<logger name="org.jaudiotagger" level="WARN"/> <!-- 音频标签库日志控制 -->
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue