Commit graph

18 commits

Author SHA1 Message Date
Huang Xin
58d4661b74
feat(ios): CarPlay support and native TTS playout with Now Playing integration (#5085)
* feat(ios): add CarPlay support for TTS

Surface active TTS sessions on Apple CarPlay, mirroring the Android Auto
integration. A CarPlaySceneDelegate presents a CPListTemplate root
showing the currently reading book (or a placeholder when idle) and
pushes the system Now Playing template. Transport and metadata ride the
existing MPNowPlayingInfoCenter / MPRemoteCommandCenter path, so no
CarPlay button is routed by hand. A new iOS-only update_carplay_state
command lets the media bridge report session start and stop, and TTS
autoplay deep links are consumed even when the target book is already
mounted, for example when the app cold restores straight into the
reader.

Registering the UIApplicationSceneManifest crashed the app at launch on
real devices: tao's configuration_for_connecting_scene_session returned
a raw borrow of a Retained UISceneConfiguration it immediately dropped,
so UIKit retained freed memory. Add the tao fork as the packages/tao
submodule (github.com/readest/tao, branch readest-0.35: tao 0.35.3 plus
only the callback returning an autoreleased strong reference) and patch
it in the workspace. Bump @tauri-apps/api to 2.11.1 and @tauri-apps/cli
to 2.11.4 for the tao 0.35/wry 0.55 stack, and advance the tauri fork
(branch readest-2.11) with a guard in the generated unlisten script
against already-removed listeners.

The tauri 2.11 stack needs two CI accommodations. First, the
build_tauri_app job overrides setup-rust-toolchain's default RUSTFLAGS
of -D warnings: cargo builds path-patched dependencies without
cap-lints, so upstream warnings in the tao fork (deprecated glib
channel calls in the Linux backend) would fail the app build; warnings
in our own crate stay enforced by the rust_lint job. Second, tauri 2.11
enforces ACL for IPC from remote origins even without an AppManifest
(upstream security fix #15266), and the webdriver harness serves the
vitest tester page from its own port, which is a remote origin, so
every custom app command was denied and 56 tauri tests failed.
Production is unaffected since real app pages are local origins.
Declare the app commands in the ACL app manifest in build.rs (generated
allow permissions committed under src-tauri/permissions/autogenerated),
grant them in capabilities/default.json for local windows, and add a
webdriver-remote capability for localhost remote origins that is pinned
out of shipped builds via app.security.capabilities in tauri.conf.json;
scripts/test-tauri.sh opts into it with a --config override. Verified
with pnpm test:tauri: 112 passed, 1 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(android): handle audio focus properly for background TTS

Request focus with a modern AudioFocusRequest (USAGE_MEDIA,
CONTENT_TYPE_SPEECH) and pause instead of ducking, matching how spoken
audio should yield to navigation prompts. Resume after transient loss
only when the loss is what paused us, forward focus transitions to the
web side as media-session play/pause events, and pause on
ACTION_AUDIO_BECOMING_NOISY so unplugging headphones does not blast the
speaker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ios): native audio playout for Edge TTS with Now Playing support

WebAudio in WKWebView renders in WebKit's GPU process under an audio
session the app cannot own, which made every system media behavior
unreliable on device: no lock-screen card, the card vanished on pause,
AirPods resumed another app, and the mute switch silenced playback.

Play the Edge TTS utterances with an in-process AVPlayer instead. The
native-tts plugin gains a deliberately dumb playout engine
(playout_enqueue, playout_control, playout_position) and the new
NativeAudioPlayer mirrors the WebAudioPlayer session interface, so
EdgeTTSClient just picks the player per platform. Word highlighting
reads the player's media clock, which AVPlayer reports in item time and
is therefore unaffected by the playback rate; rate changes stay pitch
preserving via the timeDomain pitch algorithm and inter-sentence gaps
run on a native timer immune to background JS throttling.

With the audio in the app process the plugin claims a non-mixable
.playback session with .spokenAudio mode, so navigation prompts pause
instead of duck. Handle interruption began and ended with shouldResume,
pause on route change to the speaker, and bind an MPNowPlayingSession
to the playout player so the paused state is reported explicitly and
the Now Playing slot survives a pause. The silent keep-alive player
pauses in lockstep, KVC playback state writes are simulator only where
they are required and harmful on device, and the mixWithOthers
use_background_audio calls are removed. On the web side the composite
iOS media session mirrors metadata and position into
navigator.mediaSession, remote commands map to directional play and
pause plus a toggle, and the silent unblocker element is skipped on iOS
Tauri since a playing HTMLMediaElement makes WebKit register its own
competing now-playing client.

Volume-key page flip previously defeated all of this: its handler
unconditionally reconfigured the shared session with .mixWithOthers,
and a mixable session is ineligible for the Now Playing slot, so
pausing TTS dismissed the card and handed AirPod controls to another
app. The two plugins now coordinate through in-process notifications:
native-tts announces claim and release of the audio session, and the
volume key handler leaves the session untouched while TTS owns it, so
volume keys keep flipping pages during a pause without losing the card,
and re-owns the session after TTS teardown regardless of ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 10:53:31 +02:00
Huang Xin
fc71ca9857
feat(android): upgrade in-process WebView on devices stuck on old system WebView (#4142)
Add tauri-plugin-webview-upgrade as a git submodule under
apps/readest-app/src-tauri/plugins/. On Android devices whose system
WebView is locked to an old Chromium build (Huawei phones, Moaan / Onyx
/ Kobo e-ink readers, AOSP forks without Play Store, etc.), the reader
bundle renders as a blank screen. The plugin bootstraps before
Application.onCreate via androidx.startup and redirects the in-process
WebView loader to a recent com.google.android.webview when the user has
one sideloaded — opening the only window in which WebViewUpgrade can
swap the provider, before Tauri/Wry creates any WebView.

Thresholds (minUpgradeMajor / minSupportedMajor) come from
plugins.webview-upgrade in tauri.conf.json and are baked into Kotlin
constants at Gradle build time. Below the supported threshold with no
upgrade option, the plugin shows a localized AlertDialog (15 languages,
English fallback) prompting the user to install Android System WebView.

Plugin source: https://github.com/readest/tauri-plugin-webview-upgrade

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:01:13 +02:00
Huang Xin
5a0a70a30a
feat(reader): custom dictionaries (StarDict + MDict) (#4012)
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (rust) (push) Waiting to run
PR checks / rust_lint (push) Waiting to run
PR checks / build_web_app (push) Waiting to run
PR checks / build_tauri_app (push) Waiting to run
Deploy to vercel on merge / build_and_deploy (push) Waiting to run
* feat(reader): custom dictionaries (StarDict + MDict)

Adds a pluggable dictionary provider system. Built-in Wiktionary +
Wikipedia (extracted from the legacy single-popup model into a tabbed
shell) plus user-importable StarDict (.ifo/.idx/.dict.dz/.syn) and MDict
(.mdx/.mdd) bundles.

Settings → Language → Dictionaries: import / enable / drag-reorder /
delete (delete-mode toggle mirrors CustomFonts). Drag uses @dnd-kit with
pointer/touch/keyboard sensors. Reader popup: tabbed UI, per-tab lookup
history, scroll-aware back button, last-active tab persists. Tabs grow
to natural width up to a cap, truncate with ellipsis when crowded; phantom
bold layer prevents layout shift on focus.

StarDict reader is self-contained (replaces unused foliate-js/dict.js),
with lazy random-access binary search on .idx + .syn (~420 KB Int32Array
of byte offsets vs ~10 MB of parsed JS objects), lazy DictZip chunk
decompression via fflate streaming Inflate (cmudict/eng-nld both
chunked), and an optional .idx.offsets sidecar generated at import to
skip the init scan. Cmudict 105K-entry init drops from ~10 MB heap and
2 MB IO to ~1.7 MB heap and ~500 KB IO.

MDict uses the readest/js-mdict fork (added as a submodule, consumed via
tsconfig paths so deps stay out of readest's pnpm-lock) which adds a
browser-friendly BlobScanner reading via blob.slice(...).arrayBuffer()
— slices are lazy when the Blob is Readest's NativeFile / RemoteFile.
encrypt=2 (key-info-only) MDX is fully supported via ripemd128-based
mdxDecrypt; encrypt=1 (record-block, needs user passcode) surfaces as
unsupported.

Wikipedia annotation tool removed (Wikipedia is now a tab inside the
unified popup); legacy WiktionaryPopup / WikipediaPopup deleted. Stale
annotationQuickAction === 'wikipedia' coerced to 'dictionary' on settings
load. iOS-friendly external links: skip target="_blank" on Tauri to
avoid the WebView's "open externally" path triggering the shell scope
error; the popup's container click handler routes through openUrl.

i18n: 939 strings translated across 31 locales (30 base keys + CLDR
plural forms for ar/he/sl/pl/ru/uk/ro/it/pt/fr/es).

Test fixtures bundled: cmudict (StarDict, 105K entries), eng-nld
(StarDict, smaller), and a Longman Phrasal Verbs MDX (encrypt=2).
3396 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(stardict): resolve fflate from js-mdict source for vitest + Next

js-mdict is consumed as TypeScript source via tsconfig paths from
packages/js-mdict/src/. Its sources `import 'fflate'` directly, but
fflate is only installed under apps/readest-app/node_modules — so
vite's import-analysis (and Next/Turbopack's resolver) can't find
fflate when it walks up from the redirected js-mdict source location.
CI's fresh checkout exposes this; locally a leftover
packages/js-mdict/node_modules/fflate from the old workspace setup
masked it.

Pin fflate resolution to apps/readest-app/node_modules/fflate in:
- vitest.config.mts (Vite alias)
- next.config.mjs (webpack alias + Turbopack resolveAlias — Turbopack
  rejects absolute paths so use a project-relative form)
- tsconfig.json (paths entry so tsgo / Biome see it)

Verified by deleting packages/js-mdict/node_modules locally and
re-running pnpm test (3396 pass), pnpm lint (clean), and both
pnpm build-web and a tauri-platform Next build (clean).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 19:16:36 +02:00
Huang Xin
b3333c384c
chore(fdroid): get rid of wasm binaries in fdroid build (#3677) 2026-03-29 18:31:46 +02:00
Huang Xin
3f19ce24de
chore(agent): add gstack skills (#3557) 2026-03-18 06:08:48 +01:00
Huang Xin
97cab2d70b
feat(database): support turso fts in tauri apps (#3484)
Some checks are pending
Deploy to vercel on merge / build_and_deploy (push) Waiting to run
2026-03-05 21:00:15 +01:00
Huang Xin
5273ef75dc
feat(database): add database service abstraction with libsql/turso backend (#3472) 2026-03-05 02:38:23 +08:00
Huang Xin
c792c18e01
feat: support text conversion between simplifed and traditional Chinese, closes #2508 (#2520)
Some checks are pending
Deploy to vercel on merge / build_and_deploy (push) Waiting to run
2025-11-23 09:56:12 +01:00
Huang Xin
c24f951375
fix: set xdg-mime with mime type other than scheme, closes #1621 (#1641) 2025-07-21 05:02:22 +08:00
Huang Xin
0150177a9d
chore: bump tauri to version 2.5.1 (#1138) 2025-05-13 09:30:34 +02:00
Huang Xin
f841004e7a
chore: bump update of tauri (#532) 2025-03-08 10:53:31 +01:00
chrox
c02d63367a
Update github workflow of release 2024-12-01 12:55:12 +01:00
chrox
e6e960d2b6
Remove submodule wry 2024-11-21 13:43:35 +01:00
chrox
48bd5bb29a
Remove direct dependency of pdf.js 2024-10-17 19:15:21 +02:00
chrox
1e66ab816b
Add wry as subpackage 2024-10-16 12:37:26 +02:00
chrox
b88e86be5f
Fix PDF rendering 2024-10-13 00:24:12 +02:00
chrox
39def9f36a
Update tauri dependencies 2024-10-13 00:05:15 +02:00
chrox
01ad5dbb14
Add books parsing function using foliate-js 2024-10-12 21:29:54 +02:00