diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..4472b0bb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +FFMPEG_DIR_AARCH64_APPLE_IOS = { value = "vendor/ffmpeg_ios", relative = true } +FFMPEG_DIR_aarch64_apple_ios = { value = "vendor/ffmpeg_ios", relative = true } diff --git a/.github/workflows/build-player.yaml b/.github/workflows/build-player.yaml index 95475608..dda08e70 100644 --- a/.github/workflows/build-player.yaml +++ b/.github/workflows/build-player.yaml @@ -238,7 +238,7 @@ jobs: target/**/release/amll-player build-tauri-android: - name: 构建 Tauri 安卓移动版本 + name: 构建 Tauri 安卓版本 needs: clean-pre-release runs-on: ubuntu-latest @@ -335,7 +335,7 @@ jobs: run: | node ./packages/player/scripts/gen-dev-version.mjs - - name: 构建 AMLL Player 安卓移动版程序 + - name: 构建 AMLL Player 安卓版本 env: CARGO_NDK_ANDROID_PLATFORM: 26 run: | @@ -350,3 +350,185 @@ jobs: path: | packages/player/src-tauri/gen/android/app/build/outputs/**/*.apk packages/player/src-tauri/gen/android/app/build/outputs/**/*.aab + + build-tauri-ios: + name: 构建 Tauri iOS 版本 + needs: clean-pre-release + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4 + name: 克隆仓库 + with: + fetch-depth: 0 + + - name: 选择 Xcode 16 版本 + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16' + + - name: 安装 PNPM + uses: pnpm/action-setup@v4 + + - name: 安装 Node.js + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + - name: 安装 Rust 工具链与 iOS 构建目标 + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + targets: "aarch64-apple-ios" + + - name: 配置 Rust 缓存 + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . -> target + packages/player/src-tauri -> target + key: ios-aarch64-only + + - name: 拉取并链接 iOS 预编译 FFmpeg 静态库 + shell: bash + env: + BUILDER_REPO: apoint123/ffmpeg-builder + FFMPEG_VERSION_TAG: "ffmpeg-8.0.1-cc5ef4262cfdcf5eba1e9d8b6d4020e9998c5680" + run: | + PACKAGE_NAME="ffmpeg-8.0.1-ios-arm64.tar.gz" + DOWNLOAD_URL="https://github.com/${BUILDER_REPO}/releases/download/${FFMPEG_VERSION_TAG}/${PACKAGE_NAME}" + EXTRACT_DIR="${{ github.workspace }}/vendor/ffmpeg_ios" + + echo "Downloading iOS FFmpeg static libraries from $DOWNLOAD_URL..." + curl -f -sL $DOWNLOAD_URL -o $PACKAGE_NAME + + mkdir -p $EXTRACT_DIR + echo "Extracting FFmpeg to $EXTRACT_DIR..." + tar -xzf $PACKAGE_NAME -C $EXTRACT_DIR + rm $PACKAGE_NAME + + # 设置链接编译所需的 FFMPEG_DIR 环境变量,映射真机目标 + echo "FFMPEG_DIR=$EXTRACT_DIR" >> $GITHUB_ENV + echo "FFMPEG_DIR_AARCH64_APPLE_IOS=$EXTRACT_DIR" >> $GITHUB_ENV + echo "FFMPEG_DIR_aarch64_apple_ios=$EXTRACT_DIR" >> $GITHUB_ENV + echo "FFmpeg iOS environment set up successfully." + + + - name: 安装 pnpm 依赖 + run: | + pnpm i --frozen-lockfile --ignore-scripts + env: + AMLL_GITHUB_IS_ACTION: true + + - name: 生成开发构建版本号 + run: | + node ./packages/player/scripts/gen-dev-version.mjs + env: + AMLL_IOS_BUILD: "true" + + - name: 初始化 iOS 工程并配置图标 + run: | + pnpm -F player tauri ios init + # 从高分辨率主图生成符合 iOS 规范的无切边专属图标集 + pnpm -F player tauri icon src-tauri/icons/512x512@2x.png + env: + AMLL_GITHUB_IS_ACTION: true + + - name: 禁用 Xcode 工程自动签名 (免证书构建) + shell: bash + run: | + # 修改自动签名配置为手动,强制关闭代码签名校验 + PBXPROJ="packages/player/src-tauri/gen/apple/amll-player.xcodeproj/project.pbxproj" + + node -e ' + const fs = require("fs"); + const path = process.argv[1]; + let content = fs.readFileSync(path, "utf8"); + + content = content.replace(/buildSettings = \{([\s\S]*?)\};/g, (match, body) => { + let newBody = body + .replace(/CODE_SIGN_STYLE = [^;]+;/g, "") + .replace(/DEVELOPMENT_TEAM = [^;]+;/g, "") + .replace(/CODE_SIGN_IDENTITY = [^;]+;/g, "") + .replace(/CODE_SIGNING_ALLOWED = [^;]+;/g, "") + .replace(/CODE_SIGNING_REQUIRED = [^;]+;/g, "") + .replace(/PROVISIONING_PROFILE_SPECIFIER = [^;]+;/g, "") + .split("\n") + .filter(line => line.trim() !== "") + .join("\n"); + + return `buildSettings = {\n${newBody}\n\t\t\t\tCODE_SIGNING_ALLOWED = NO;\n\t\t\t\tCODE_SIGNING_REQUIRED = NO;\n\t\t\t\tCODE_SIGN_IDENTITY = "";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tDEVELOPMENT_TEAM = "";\n\t\t\t};`; + }); + + fs.writeFileSync(path, content, "utf8"); + console.log("Successfully rewrote all buildSettings in project.pbxproj to disable signing."); + ' "$PBXPROJ" + + - name: 构建前端资源 + run: pnpm -F player build + env: + AMLL_GITHUB_IS_ACTION: true + + - name: 注入 iOS 后台音频播放权限 (Info.plist) + shell: bash + run: | + # 在 Xcode 工程生成后,通过 PlistBuddy 写入 UIBackgroundModes=audio 权限,支持后台切歌与播放 + PLIST_PATH="packages/player/src-tauri/gen/apple/amll-player_iOS/Info.plist" + /usr/libexec/PlistBuddy -c "Add :UIBackgroundModes array" "$PLIST_PATH" || true + /usr/libexec/PlistBuddy -c "Add :UIBackgroundModes:0 string audio" "$PLIST_PATH" || true + echo "Info.plist background audio modes injected." + + - name: 使用 Tauri CLI 构建无签名 iOS 应用 + shell: bash + run: | + # 使用 Tauri CLI 编译应用。无签名构建在导出归档时会提示报错(code 70), + # 若底层的 *.app 已经成功生成,我们捕获该状态并作为构建成功处理。 + pnpm -F player tauri ios build || { + echo "==== 已编译的 iOS 应用包 (.app) 路径列表 ====" + find packages/player/src-tauri/gen/apple -name "*.app" 2>/dev/null || true + find $HOME/Library/Developer/Xcode -name "*.app" 2>/dev/null || true + echo "==========================================" + + APP_PATH=$(find packages/player/src-tauri/gen/apple -name "*.app" 2>/dev/null | head -1) + if [ -z "$APP_PATH" ]; then + APP_PATH=$(find $HOME/Library/Developer/Xcode/DerivedData $HOME/Library/Developer/Xcode/Archives -name "*.app" 2>/dev/null | head -1) + fi + if [ -n "$APP_PATH" ] && [ -d "$APP_PATH" ]; then + echo "Tauri CLI exited with signing/export warning, but iOS app was compiled successfully!" + echo "Found compiled app at: $APP_PATH" + echo "Proceeding with manual unsigned packaging..." + else + echo "ERROR: Tauri CLI build failed and no compiled .app was found." + exit 1 + fi + } + env: + AMLL_GITHUB_IS_ACTION: true + SKIP_FRONTEND_BUILD: true + + - name: 压包并生成无签名版 .ipa + run: | + # 动态查找编译生成的 .app 目录并打包为无签名 .ipa 产物 + APP_PATH=$(find packages/player/src-tauri/gen/apple -name "*.app" 2>/dev/null | head -1) + if [ -z "$APP_PATH" ]; then + APP_PATH=$(find $HOME/Library/Developer/Xcode/DerivedData $HOME/Library/Developer/Xcode/Archives -name "*.app" 2>/dev/null | head -1) + fi + if [ -z "$APP_PATH" ]; then + echo "ERROR: Compiled .app not found. Listing possible locations:" + find . -name "*.app" 2>/dev/null + exit 1 + fi + echo "Found: $APP_PATH" + + mkdir -p build/Payload + cp -r "$APP_PATH" build/Payload/ + cd build && zip -r amll-player-unsigned.ipa Payload + echo "IPA created:" + ls -la amll-player-unsigned.ipa + + - name: 上传无签名 ipa 包至 GitHub Action Artifacts + uses: actions/upload-artifact@v4 + with: + name: AMLL Player iOS Unsigned IPA + path: build/amll-player-unsigned.ipa diff --git a/Cargo.lock b/Cargo.lock index 201f2db4..6944f09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,7 @@ dependencies = [ "ndk-context", "objc2", "objc2-avf-audio", + "objc2-foundation", "reqwest 0.13.3", "rkyv", "rodio", @@ -1530,7 +1531,7 @@ dependencies = [ [[package]] name = "ffmpeg-sys-next" version = "8.1.0" -source = "git+https://github.com/apoint123/rust-ffmpeg-sys#65d35b3b7d34f20b37e310165a41548db12a4833" +source = "git+https://github.com/apoint123/rust-ffmpeg-sys#1c40a58859bd32b3e51acad2a6675b59d29d8a74" dependencies = [ "bindgen", "cc", diff --git a/packages/player/scripts/gen-dev-version.mjs b/packages/player/scripts/gen-dev-version.mjs index eeb5b3fc..3eee0ce4 100644 --- a/packages/player/scripts/gen-dev-version.mjs +++ b/packages/player/scripts/gen-dev-version.mjs @@ -29,10 +29,20 @@ if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(baseVersion)) { // const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); const commitCount = execSync("git rev-list --count HEAD").toString().trim(); -const devVersion = `${baseVersion}+${commitCount}`; - -tauriConf.version = devVersion; - -console.log(`Generated dev version: ${baseVersion} -> ${devVersion}`); +const isIos = process.env.AMLL_IOS_BUILD === "true"; + +if (isIos) { + // iOS:版本号严格保持 3 位,构建号单独写入 bundle.iOS.bundleVersion + tauriConf.version = baseVersion; + if (!tauriConf.bundle) tauriConf.bundle = {}; + if (!tauriConf.bundle.iOS) tauriConf.bundle.iOS = {}; + tauriConf.bundle.iOS.bundleVersion = commitCount; + console.log(`Generated iOS dev version: ${baseVersion} (${commitCount})`); +} else { + // 其它平台:保留原有逻辑 + const devVersion = `${baseVersion}+${commitCount}`; + tauriConf.version = devVersion; + console.log(`Generated dev version: ${baseVersion} -> ${devVersion}`); +} writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, "\t")); diff --git a/packages/player/src-tauri/Cargo.toml b/packages/player/src-tauri/Cargo.toml index 9a71b886..8fe284be 100644 --- a/packages/player/src-tauri/Cargo.toml +++ b/packages/player/src-tauri/Cargo.toml @@ -75,6 +75,11 @@ objc2 = "^0.6" objc2-avf-audio = "^0.3" tauri-plugin-macos-fps = "0.1.0" +[target.'cfg(target_os = "ios")'.dependencies] +objc2 = "^0.6" +objc2-avf-audio = "^0.3" +objc2-foundation = "^0.3" + [target.'cfg(target_os = "windows")'.dependencies] webview2-com = "0.38" taskbar-lyric = { git = "https://github.com/apoint123/taskbar-lyric.git", features = [ diff --git a/packages/player/src-tauri/src/lib.rs b/packages/player/src-tauri/src/lib.rs index a29ce3d3..0b068435 100644 --- a/packages/player/src-tauri/src/lib.rs +++ b/packages/player/src-tauri/src/lib.rs @@ -527,6 +527,23 @@ pub fn run() { theme_watcher::get_system_theme ]) .setup(|app| { + #[cfg(target_os = "ios")] + { + use objc2::msg_send; + use objc2_avf_audio::AVAudioSession; + use objc2_foundation::ns_string; + + info!("Initializing iOS AVAudioSession Category to Playback..."); + unsafe { + let session = AVAudioSession::sharedInstance(); + let category = ns_string!("AVAudioSessionCategoryPlayback"); + + let _: () = msg_send![&session, setCategory: category, error: std::ptr::null_mut::<*mut *mut objc2_foundation::NSError>()]; + + let _: bool = msg_send![&session, setActive: true, error: std::ptr::null_mut::<*mut *mut objc2_foundation::NSError>()]; + } + info!("iOS AVAudioSession Category set to Playback successfully!"); + } #[cfg(target_os = "android")] { if let Some(webview) = app.get_webview_window("main") { diff --git a/packages/player/src-tauri/tauri.conf.json b/packages/player/src-tauri/tauri.conf.json index 93a09542..8a31fddd 100644 --- a/packages/player/src-tauri/tauri.conf.json +++ b/packages/player/src-tauri/tauri.conf.json @@ -32,7 +32,6 @@ "version": "0.0.1", "identifier": "net.stevexmh.amllplayer", "plugins": { - "macos-fps": {}, "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVBMzY2M0I2Qjc4MjBBNTUKUldSVkNvSzN0bU0yV2k0bUhxcm9jczBNYk9lTllYaTFyWHVEZ2xaSloxaVhpSnQ1RHZFSUtnS2IK", "endpoints": [ diff --git a/packages/player/src-tauri/tauri.macos.conf.json b/packages/player/src-tauri/tauri.macos.conf.json new file mode 100644 index 00000000..26cdf2f3 --- /dev/null +++ b/packages/player/src-tauri/tauri.macos.conf.json @@ -0,0 +1,5 @@ +{ + "plugins": { + "macos-fps": {} + } +}