diff --git a/.github/workflows/build-and-make.yaml b/.github/workflows/build-and-make.yaml index 3e4ff1925..36ace9e00 100644 --- a/.github/workflows/build-and-make.yaml +++ b/.github/workflows/build-and-make.yaml @@ -117,33 +117,45 @@ jobs: const crypto = require('crypto'); const fs = require('fs'); - const deploymentTarget = process.env.MACOSX_DEPLOYMENT_TARGET || '11.0'; - const safeDeploymentTarget = deploymentTarget.replace(/[^A-Za-z0-9_.-]/g, '-'); - let xcodeVersion = 'none'; - try { - xcodeVersion = childProcess - .execSync('xcodebuild -version', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) - .replace(/\s+$/g, '') - .replace(/\n/g, ' '); - } catch { - xcodeVersion = 'none'; - } const hash = (value) => crypto.createHash('sha256').update(value).digest('hex'); - const sourceHash = hash([ - 'tools/embedded-mpv/build-macos-runtime.mjs', - 'tools/embedded-mpv/stage-macos-runtime.mjs', + const targetPlatform = '${{ matrix.embedded_mpv_platform }}'; + const targetArch = '${{ matrix.embedded_mpv_arch }}'; + const sourceHashFiles = [ 'tools/embedded-mpv/stage-runtime.mjs', - ].map((filePath) => fs.readFileSync(filePath)).join('\n')); - const xcodeHash = hash(xcodeVersion); - const cacheKey = [ + ]; + const cacheKeyParts = [ 'embedded-mpv-runtime-v2', - '${{ matrix.embedded_mpv_platform }}', - '${{ matrix.embedded_mpv_arch }}', + targetPlatform, + targetArch, process.env.RUNNER_OS, - `macos${safeDeploymentTarget}`, - `xcode${xcodeHash}`, - sourceHash, - ].join('-'); + ]; + let deploymentTarget = ''; + let xcodeVersion = 'none'; + if (targetPlatform === 'darwin') { + deploymentTarget = process.env.MACOSX_DEPLOYMENT_TARGET || '11.0'; + const safeDeploymentTarget = deploymentTarget.replace(/[^A-Za-z0-9_.-]/g, '-'); + sourceHashFiles.push( + 'tools/embedded-mpv/build-macos-runtime.mjs', + 'tools/embedded-mpv/stage-macos-runtime.mjs' + ); + try { + xcodeVersion = childProcess + .execSync('xcodebuild -version', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + .replace(/\s+$/g, '') + .replace(/\n/g, ' '); + } catch { + xcodeVersion = 'none'; + } + cacheKeyParts.push( + `macos${safeDeploymentTarget}`, + `xcode${hash(xcodeVersion)}` + ); + } + const sourceHash = hash( + sourceHashFiles.map((filePath) => fs.readFileSync(filePath)).join('\n') + ); + cacheKeyParts.push(sourceHash); + const cacheKey = cacheKeyParts.join('-'); fs.appendFileSync(process.env.GITHUB_OUTPUT, `deployment-target=${deploymentTarget}\n`); fs.appendFileSync(process.env.GITHUB_OUTPUT, `xcode-version=${xcodeVersion}\n`); diff --git a/apps/electron-backend/build-embedded-mpv.js b/apps/electron-backend/build-embedded-mpv.js index 23db9f536..c12ad9b5f 100644 --- a/apps/electron-backend/build-embedded-mpv.js +++ b/apps/electron-backend/build-embedded-mpv.js @@ -6,7 +6,7 @@ const { findLibMpv, patchAddonForBundledRuntime, validateNoForbiddenRuntimeLinks, -} = require('../../tools/packaging/embedded-mpv-macos.cjs'); +} = require('../../tools/packaging/embedded-mpv-packaging.cjs'); const workspaceRoot = process.cwd(); const addonRoot = path.join(workspaceRoot, 'apps', 'electron-backend', 'native'); diff --git a/apps/electron-backend/native/src/embedded_mpv_wid_common.h b/apps/electron-backend/native/src/embedded_mpv_wid_common.h index b17940bc4..a1d2a7489 100644 --- a/apps/electron-backend/native/src/embedded_mpv_wid_common.h +++ b/apps/electron-backend/native/src/embedded_mpv_wid_common.h @@ -508,6 +508,7 @@ void runEventLoop(std::shared_ptr session) session->snapshot.status == SessionStatus::Paused ? SessionStatus::Paused : SessionStatus::Playing; + session->snapshot.error.clear(); break; case MPV_EVENT_END_FILE: { const auto* endFile = @@ -538,6 +539,7 @@ void runEventLoop(std::shared_ptr session) session->snapshot.status = paused ? SessionStatus::Paused : SessionStatus::Playing; + session->snapshot.error.clear(); } else if (name == "volume" && property->format == MPV_FORMAT_DOUBLE) { session->snapshot.volumePercent = *static_cast(property->data); diff --git a/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts index 999d0fc8e..28c562b48 100644 --- a/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts +++ b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts @@ -25,6 +25,10 @@ describe('Embedded MPV native source recording invariants', () => { path.resolve(__dirname, '../../../build-embedded-mpv.js'), 'utf8' ); + const buildAndMakeWorkflowSource = readFileSync( + path.resolve(__dirname, '../../../../../.github/workflows/build-and-make.yaml'), + 'utf8' + ); function functionBody(name: string): string { const start = nativeSource.indexOf(`Napi::Value ${name}(`); @@ -99,6 +103,42 @@ describe('Embedded MPV native source recording invariants', () => { ); }); + it('clears transient Windows/Linux MPV operation errors after healthy playback states', () => { + const fileLoadedCaseStart = widCommonSource.indexOf( + 'case MPV_EVENT_FILE_LOADED:' + ); + expect(fileLoadedCaseStart).toBeGreaterThanOrEqual(0); + const endFileCaseStart = widCommonSource.indexOf( + 'case MPV_EVENT_END_FILE:', + fileLoadedCaseStart + ); + expect(endFileCaseStart).toBeGreaterThan(fileLoadedCaseStart); + const fileLoadedCase = widCommonSource.slice( + fileLoadedCaseStart, + endFileCaseStart + ); + + expect(fileLoadedCase).toContain('session->snapshot.error.clear();'); + + const pausePropertyStart = widCommonSource.indexOf( + 'const bool paused = *static_cast(property->data) != 0;' + ); + expect(pausePropertyStart).toBeGreaterThanOrEqual(0); + const volumePropertyStart = widCommonSource.indexOf( + '} else if (name == "volume"', + pausePropertyStart + ); + expect(volumePropertyStart).toBeGreaterThan(pausePropertyStart); + const pausePropertyBranch = widCommonSource.slice( + pausePropertyStart, + volumePropertyStart + ); + + expect(pausePropertyBranch).toContain( + 'session->snapshot.error.clear();' + ); + }); + it('copies Windows runtime DLLs next to the addon for Windows loader lookup', () => { expect(buildScriptSource).toContain( "for (const windowsDllName of ['mpv-2.dll', 'mpv.dll'])" @@ -124,6 +164,25 @@ describe('Embedded MPV native source recording invariants', () => { expect(linuxSource).toContain('XNextEvent(display_, &event)'); expect(linuxSource).toContain('drainEvents();'); }); + + it('uses platform-specific embedded MPV runtime cache key inputs in CI', () => { + expect(buildAndMakeWorkflowSource).toContain( + "const targetPlatform = '${{ matrix.embedded_mpv_platform }}';" + ); + expect(buildAndMakeWorkflowSource).toContain( + "if (targetPlatform === 'darwin')" + ); + expect(buildAndMakeWorkflowSource).toContain( + "'tools/embedded-mpv/build-macos-runtime.mjs'" + ); + expect(buildAndMakeWorkflowSource).toContain( + "'tools/embedded-mpv/stage-runtime.mjs'" + ); + expect(buildAndMakeWorkflowSource).not.toContain( + '`macos${safeDeploymentTarget}`,\n' + + ' `xcode${xcodeHash}`,' + ); + }); }); describe('Embedded MPV native build configuration', () => { diff --git a/tools/packaging/electron-after-pack.cjs b/tools/packaging/electron-after-pack.cjs index 625263156..24bbcd040 100644 --- a/tools/packaging/electron-after-pack.cjs +++ b/tools/packaging/electron-after-pack.cjs @@ -1,7 +1,7 @@ const linuxAfterPack = require('./linux-after-pack.cjs'); const { validatePackagedEmbeddedMpv, -} = require('./embedded-mpv-macos.cjs'); +} = require('./embedded-mpv-packaging.cjs'); const fs = require('fs'); const path = require('path'); diff --git a/tools/packaging/electron-package-identity.test.mjs b/tools/packaging/electron-package-identity.test.mjs index ffba9a33a..0bbf5e6d9 100644 --- a/tools/packaging/electron-package-identity.test.mjs +++ b/tools/packaging/electron-package-identity.test.mjs @@ -45,9 +45,13 @@ const packageLayoutVerifier = fs.readFileSync( join(currentDir, 'verify-electron-package-layout.mjs'), 'utf8' ); +const electronAfterPackSource = fs.readFileSync( + join(currentDir, 'electron-after-pack.cjs'), + 'utf8' +); const { validatePackagedEmbeddedMpv, -} = require('./embedded-mpv-macos.cjs'); +} = require('./embedded-mpv-packaging.cjs'); test('Linux package identity does not expose the internal Electron backend project name', () => { assert.equal(electronBuilderConfig.productName, 'IPTVnator'); @@ -222,6 +226,17 @@ test('embedded MPV package validation accepts Windows and Linux runtime files', } }); +test('embedded MPV packaging helpers use a cross-platform module name', () => { + assert.match( + electronAfterPackSource, + /require\(['"]\.\/embedded-mpv-packaging\.cjs['"]\)/ + ); + assert.match( + packageLayoutVerifier, + /require\(['"]\.\/embedded-mpv-packaging\.cjs['"]\)/ + ); +}); + test('embedded MPV package validation rejects missing required Windows runtime', () => { const tempDir = fs.mkdtempSync(join(os.tmpdir(), 'iptvnator-mpv-package-')); diff --git a/tools/packaging/embedded-mpv-macos.cjs b/tools/packaging/embedded-mpv-packaging.cjs similarity index 100% rename from tools/packaging/embedded-mpv-macos.cjs rename to tools/packaging/embedded-mpv-packaging.cjs diff --git a/tools/packaging/verify-electron-package-layout.mjs b/tools/packaging/verify-electron-package-layout.mjs index 177a20bec..0e7607567 100644 --- a/tools/packaging/verify-electron-package-layout.mjs +++ b/tools/packaging/verify-electron-package-layout.mjs @@ -6,7 +6,7 @@ import { buildElectronBuilderMetadata } from './generate-electron-builder-metada const require = createRequire(import.meta.url); const { extractFile } = require('@electron/asar'); -const { validatePackagedEmbeddedMpv } = require('./embedded-mpv-macos.cjs'); +const { validatePackagedEmbeddedMpv } = require('./embedded-mpv-packaging.cjs'); const args = process.argv.slice(2); const normalizedArgs = args[0] === '--' ? args.slice(1) : args; const [platform, arch = ''] = normalizedArgs;