231 lines
5.6 KiB
TypeScript
231 lines
5.6 KiB
TypeScript
/**
|
|
* Apple Music 控制和状态获取模块 (Deno 版本)
|
|
* 负责与 Apple Music 应用交互的所有功能
|
|
*/
|
|
|
|
export interface MusicStatus {
|
|
status: "playing" | "paused" | "stopped" | "notrunning" | "error";
|
|
track_name?: string;
|
|
artist?: string;
|
|
position?: number;
|
|
duration?: number;
|
|
error?: string;
|
|
}
|
|
|
|
export interface ControlResult {
|
|
status: "success" | "error";
|
|
message?: string;
|
|
}
|
|
|
|
/**
|
|
* 运行 AppleScript 并返回结果
|
|
*/
|
|
async function runAppleScript(script: string): Promise<string> {
|
|
const process = new Deno.Command("osascript", {
|
|
args: ["-e", script],
|
|
stdout: "piped",
|
|
stderr: "piped",
|
|
});
|
|
|
|
try {
|
|
const { code, stdout, stderr } = await process.output();
|
|
const output = new TextDecoder().decode(stdout).trim();
|
|
const error = new TextDecoder().decode(stderr).trim();
|
|
|
|
if (code !== 0) {
|
|
console.warn(`AppleScript 执行失败: ${error}`);
|
|
return "";
|
|
}
|
|
|
|
return output;
|
|
} catch (error) {
|
|
console.error(`执行 AppleScript 时发生错误: ${error}`);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取 Apple Music 播放状态 - 适配 macOS Tahoe
|
|
*/
|
|
export async function getMusicStatus(): Promise<MusicStatus> {
|
|
// 先获取播放状态
|
|
const statusScript = `
|
|
tell application "Music"
|
|
if it is running then
|
|
set playerState to (get player state) as string
|
|
return playerState
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
|
|
try {
|
|
const playerState = await runAppleScript(statusScript);
|
|
|
|
if (!playerState) {
|
|
console.error("AppleScript 执行失败,返回了空值");
|
|
return { status: "error", error: "AppleScript execution failed" };
|
|
}
|
|
|
|
if (playerState === "notrunning") {
|
|
return { status: "notrunning" };
|
|
}
|
|
|
|
if (playerState === "stopped") {
|
|
return { status: "stopped" };
|
|
}
|
|
|
|
// 尝试获取歌曲信息
|
|
try {
|
|
const infoScript = `
|
|
tell application "Music"
|
|
if it is running then
|
|
try
|
|
set trackName to ""
|
|
set artistName to ""
|
|
set pos to 0
|
|
set dur to 0
|
|
|
|
if exists current track then
|
|
set t to current track
|
|
try
|
|
set trackName to (name of t) as text
|
|
end try
|
|
try
|
|
set artistName to (artist of t) as text
|
|
end try
|
|
try
|
|
set dur to (duration of t) as real
|
|
end try
|
|
end if
|
|
|
|
try
|
|
set pos to (player position) as real
|
|
end try
|
|
|
|
return "success|||" & trackName & "|||" & artistName & "|||" & (pos as text) & "|||" & (dur as text)
|
|
on error errMsg
|
|
return "error|||" & errMsg
|
|
end try
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
|
|
const infoResult = await runAppleScript(infoScript);
|
|
|
|
if (infoResult && infoResult.startsWith("success|||")) {
|
|
const parts = infoResult.split("|||");
|
|
if (parts.length >= 5) {
|
|
return {
|
|
status: playerState as "playing" | "paused",
|
|
track_name: parts[1],
|
|
artist: parts[2],
|
|
position: parseFloat(parts[3]),
|
|
duration: parseFloat(parts[4]),
|
|
};
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(`获取歌曲信息失败: ${error}`);
|
|
}
|
|
|
|
// 如果无法获取歌曲信息,至少返回播放状态
|
|
return {
|
|
status: playerState as "playing" | "paused",
|
|
track_name: "无法获取歌曲信息",
|
|
artist: "无法获取艺术家信息",
|
|
position: 0,
|
|
duration: 0,
|
|
};
|
|
} catch (error) {
|
|
console.error(`获取音乐状态时发生异常: ${error}`);
|
|
return { status: "error", error: String(error) };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 控制 Apple Music 播放
|
|
*/
|
|
export async function controlMusic(
|
|
action: string,
|
|
position?: number
|
|
): Promise<ControlResult> {
|
|
let script = "";
|
|
|
|
try {
|
|
switch (action) {
|
|
case "playpause":
|
|
script = `
|
|
tell application "Music"
|
|
if it is running then
|
|
playpause
|
|
return "ok"
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
break;
|
|
|
|
case "previous":
|
|
script = `
|
|
tell application "Music"
|
|
if it is running then
|
|
previous track
|
|
return "ok"
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
break;
|
|
|
|
case "next":
|
|
script = `
|
|
tell application "Music"
|
|
if it is running then
|
|
next track
|
|
return "ok"
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
break;
|
|
|
|
case "seek":
|
|
if (position !== undefined) {
|
|
script = `
|
|
tell application "Music"
|
|
if it is running then
|
|
set player position to ${position}
|
|
return "ok"
|
|
else
|
|
return "notrunning"
|
|
end if
|
|
end tell
|
|
`;
|
|
} else {
|
|
return { status: "error", message: "位置参数不能为空" };
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return { status: "error", message: "未知的命令" };
|
|
}
|
|
|
|
const result = await runAppleScript(script);
|
|
|
|
if (result === "ok") {
|
|
return { status: "success" };
|
|
} else {
|
|
return { status: "error", message: result };
|
|
}
|
|
} catch (error) {
|
|
return { status: "error", message: String(error) };
|
|
}
|
|
}
|