mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* fix(playback): harden embedded mpv session handling and support detection - guard the session controller against late startup rejections clobbering a newer session during fast channel zapping - exclude the refresh timestamp from the session-update dedup key so idle sessions stop re-emitting IPC updates every 500 ms - macOS: reconcile async loadfile replies by request id so a rejected seek/aid/speed on a live stream no longer flips the session to error - append --ozone-platform=x11 on Linux in main.ts so direct binary and AppImage launches match the packaged .desktop launcher behavior - return a sandbox-specific unsupported reason in Flatpak/Snap instead of asking the user to install mpv inside the sandbox - update the stale "macOS only" embedded MPV claim in CLAUDE.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): populate Linux audio tracks and fix ARM Linux packaging - Linux poller now reads track-list/count each tick and walks the scalar track-list/N/* sub-properties when the count changes, so the audio-track menu is no longer empty; selection reconciles from the aid property - afterPack replaces the x64 embedded_mpv.node with an embedded-mpv-unavailable.txt marker in arm64/armv7l Linux packages, and package-layout verification rejects foreign-architecture addons while requiring the marker - extend native source invariants for the non-fatal async-reply rule (macOS) and the Linux track-list polling contract - document the Linux track-list mechanics and ARM packaging behavior in docs/architecture/embedded-mpv-native.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): embedded mpv player UX polish and full localization - click on the video toggles pause (same action as Space) with a 250 ms grace period so double-click fullscreen cancels the pending pause; no DOM overlay is drawn — the dock transport icon is the feedback - timeline scrubbing previews the drag position locally and commits a single seek on release instead of one IPC seek per drag pixel - translate all player UI strings (controls, tooltips, aria-labels, status and recording messages) via new EMBEDDED_MPV.PLAYER.* keys, synced across en + 17 locales through the i18n-fill workflow - replace the legacy @Output() EventEmitter with the signal output() API Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): react to language changes and respect ozone platform hint Address review feedback on #1122: - add a translationsTick signal (onLangChange/onTranslationChange/ onDefaultLangChange) read by every computed() and template helper that calls translate.instant(), so labels re-evaluate on a runtime language switch and when the translation file finishes loading after mount - suppress the Linux --ozone-platform=x11 fallback when the user set ELECTRON_OZONE_PLATFORM_HINT, matching the existing respect for an explicit --ozone-platform switch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
126 lines
4.2 KiB
JavaScript
126 lines
4.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
import os from 'node:os';
|
|
import { join } from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const {
|
|
isForeignLinuxEmbeddedMpvArch,
|
|
linuxUnpackedDirArch,
|
|
resolveElectronBuilderArchName,
|
|
validatePackagedEmbeddedMpv,
|
|
} = require('./embedded-mpv-packaging.cjs');
|
|
|
|
const X64_ADDON_ENV = { IPTVNATOR_EMBEDDED_MPV_ARCH: 'x64' };
|
|
|
|
test('resolveElectronBuilderArchName maps builder-util Arch enum values', () => {
|
|
assert.equal(resolveElectronBuilderArchName(0), 'ia32');
|
|
assert.equal(resolveElectronBuilderArchName(1), 'x64');
|
|
assert.equal(resolveElectronBuilderArchName(2), 'armv7l');
|
|
assert.equal(resolveElectronBuilderArchName(3), 'arm64');
|
|
assert.equal(resolveElectronBuilderArchName('arm64'), 'arm64');
|
|
assert.equal(resolveElectronBuilderArchName(99), null);
|
|
});
|
|
|
|
test('flags Linux packages whose arch differs from the built addon', () => {
|
|
assert.equal(isForeignLinuxEmbeddedMpvArch('linux', 3, X64_ADDON_ENV), true);
|
|
assert.equal(
|
|
isForeignLinuxEmbeddedMpvArch('linux', 'armv7l', X64_ADDON_ENV),
|
|
true
|
|
);
|
|
assert.equal(
|
|
isForeignLinuxEmbeddedMpvArch('linux', 'x64', X64_ADDON_ENV),
|
|
false
|
|
);
|
|
// Only Linux fans out foreign arches from one dist tree.
|
|
assert.equal(
|
|
isForeignLinuxEmbeddedMpvArch('darwin', 'arm64', X64_ADDON_ENV),
|
|
false
|
|
);
|
|
assert.equal(
|
|
isForeignLinuxEmbeddedMpvArch('win32', 'arm64', X64_ADDON_ENV),
|
|
false
|
|
);
|
|
});
|
|
|
|
test('derives package arch from electron-builder Linux output directory names', () => {
|
|
assert.equal(linuxUnpackedDirArch('linux-unpacked'), 'x64');
|
|
assert.equal(linuxUnpackedDirArch('linux-arm64-unpacked'), 'arm64');
|
|
assert.equal(linuxUnpackedDirArch('linux-armv7l-unpacked'), 'armv7l');
|
|
assert.equal(linuxUnpackedDirArch('mac-arm64'), null);
|
|
assert.equal(linuxUnpackedDirArch('win-unpacked'), null);
|
|
});
|
|
|
|
function createResourceDir(files) {
|
|
const resourceDir = fs.mkdtempSync(
|
|
join(os.tmpdir(), 'iptvnator-embedded-mpv-arch-')
|
|
);
|
|
const nativeDir = join(
|
|
resourceDir,
|
|
'app.asar.unpacked',
|
|
'electron-backend',
|
|
'native'
|
|
);
|
|
fs.mkdirSync(nativeDir, { recursive: true });
|
|
for (const [name, content] of Object.entries(files)) {
|
|
fs.writeFileSync(join(nativeDir, name), content);
|
|
}
|
|
return resourceDir;
|
|
}
|
|
|
|
test('foreign-arch validation requires the unavailable marker and forbids the addon', (t) => {
|
|
const withMarker = createResourceDir({
|
|
'embedded-mpv-unavailable.txt': 'not bundled for arm64\n',
|
|
});
|
|
const withAddon = createResourceDir({
|
|
'embedded-mpv-unavailable.txt': 'not bundled for arm64\n',
|
|
'embedded_mpv.node': 'x64 machine code',
|
|
});
|
|
const empty = createResourceDir({});
|
|
t.after(() => {
|
|
for (const dir of [withMarker, withAddon, empty]) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
assert.deepEqual(
|
|
validatePackagedEmbeddedMpv(withMarker, {
|
|
platform: 'linux',
|
|
required: true,
|
|
foreignArch: true,
|
|
}),
|
|
[]
|
|
);
|
|
|
|
const addonErrors = validatePackagedEmbeddedMpv(withAddon, {
|
|
platform: 'linux',
|
|
required: true,
|
|
foreignArch: true,
|
|
});
|
|
assert.equal(addonErrors.length, 1);
|
|
assert.match(addonErrors[0], /must not ship in foreign-architecture/);
|
|
|
|
const markerErrors = validatePackagedEmbeddedMpv(empty, {
|
|
platform: 'linux',
|
|
required: true,
|
|
foreignArch: true,
|
|
});
|
|
assert.equal(markerErrors.length, 1);
|
|
assert.match(markerErrors[0], /unavailable marker/);
|
|
});
|
|
|
|
test('same-arch validation still requires the addon when embedded MPV is required', (t) => {
|
|
const empty = createResourceDir({});
|
|
t.after(() => fs.rmSync(empty, { recursive: true, force: true }));
|
|
|
|
const errors = validatePackagedEmbeddedMpv(empty, {
|
|
platform: 'linux',
|
|
required: true,
|
|
foreignArch: false,
|
|
});
|
|
assert.equal(errors.length, 1);
|
|
assert.match(errors[0], /Missing embedded MPV native addon/);
|
|
});
|