Device shells: <recorder> mic capture + engine-drawn <audio> control - #18
Merged
Merged
Conversation
Ports the <recorder> control to iOS. Since FPC's iOS target has no AVFoundation unit, the capture is app-layer Obj-C (like ImageLoader.m), not a Pascal objcclass shell: - tina4ios.pas: new exported `tina4_set_recording(path)` → TinaSetRecording (the core stamps the control, routes the clip into <audio id="rec">, fires onrecord; '' rolls a failed start back to idle). tina4.h gains TINA_RECORD_START/STOP and the decl. - Tina4View.m: on TINA_RECORD_START/STOP it starts/stops an AVAudioRecorder (AAC .m4a in the temp dir) after requesting the mic permission via AVAudioSession (PlayAndRecord); the file path (or '') is handed back. - Info.plist: NSMicrophoneUsageDescription. - showcase.html: a <recorder> + <audio id="rec"> demo section. Verified on device (iPhone 12 Pro): tap-record → speak → tap-stop saves a real tina4-rec-*.m4a and loads it into the audio player (no rollback, so capture + permission + hand-off all succeeded). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
syncVideos built the player URL with URLWithString:, which returns a scheme-less URL for a local file path — so the <recorder>'s temp-dir .m4a saved but the <audio id="rec"> player couldn't open it (no playback). A src starting with '/' now goes through fileURLWithPath:. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports the <recorder> control to Android:
- tina4jni.pas: new exported JNI `Tina4View_nativeSetRecording` →
TinaSetRecording ('' rolls a failed capture back to idle).
- MainActivity: startRecording/stopRecording drive a MediaRecorder
(MIC → AAC .m4a in filesDir), requesting RECORD_AUDIO at runtime
(starts on grant, rolls back on denial). AndroidManifest gains
RECORD_AUDIO.
- Tina4View: touch codes 6/7 (TINA_RECORD_START/STOP) delegate to
MainActivity; onRecordingDone hands the path back. syncVideos now
builds a file:// Uri for a local path so the recorded clip plays in
the <audio id="rec"> player (same local-file fix as iOS).
- showcase.html: a <recorder> + <audio id="rec"> demo section.
Build-verified: libtina4.so (arm64-v8a + armeabi-v7a) and the debug APK
both compile. On-device run pending an adb-visible emulator
(`adb install -r android/tina4pascal-debug.apk`, then tap the recorder).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A separate parameterless Env() helper in the Android notify unit was
elided by -O2 and `env` aliased to AndroidNotify's first string param, so
`env^.FindClass` dereferenced the notification title ("Saved a recording")
as a JNIEnv vtable and crashed (SEGV_MAPERR, fault addr = the string
bytes). This fired on <recorder> stop, whose onrecord runs notify.show.
Inline the GetEnv/AttachCurrentThread acquisition into AndroidNotify so
there is no small function for the optimiser to fold away; env is now a
real interface pointer. Verified on emulator-5554: recorder stop posts the
"Saved a recording" notification, process stays alive, no SIGSEGV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
<audio controls> was a light placeholder box with a shell-owned native player laid over it — on iOS that player was an AVPlayerViewController, a black video surface with no usable controls. Replace it with a control the engine draws itself, so it looks the same everywhere and shows a real play/stop button. Core: a new ckAudio control kind; PaintAudioControl draws a light bar with a round play/pause button and a progress track (reads the 'playing' and 'progress' attributes). A tap returns TINA_AUDIO_TOGGLE; <audio> is no longer collected as a native embed. New engine API TinaAudioSrc / TinaAudioWantPlay / TinaSetAudioProgress lets a shell drive playback and push the elapsed fraction + sounding state back each frame. Contract: AudioPlay / AudioPause / AudioProgress on TTina4Shell (safe defaults → a shell with no audio out just draws an idle bar). Shells: iOS + macOS play through AVAudioPlayer, Android through MediaPlayer (a relative asset src is resolved against the asset base); the desktop htmlviewer harness drives it via the shell contract and polls progress in its tick loop. iOS also drops the recorder's separate auto-play — the clip now lands in the control and the user taps play, one playback path. Verified: test_dom, compliance 219/219, leakcheck clean, test_interact 39/39; the control renders on the macOS renderer and plays live on Android (tap plays a tone, the bar advances, pause works, resets at end, no crash). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the on-device
<recorder>work and fixes two device bugs found while testing on a real iPhone and the Android emulator.What's in here
<recorder>mic capture — iOSAVAudioRecorder(app-layer Obj-C, since FPC's-Tioshas no AVFoundation unit) and AndroidMediaRecorderover JNI, both to an AAC.m4a.TinaSetRecordingroutes the clip into an<audio id="rec">player and firesonrecord.notify.showSIGSEGV — a real FPC-O2miscompile: the notify unit's small parameterlessEnv()helper was folded away andenvaliased the first string param, soenv^.FindClassdereferenced the notification title as a JNIEnv. Diagnosed from the tombstone (fault addr = the ASCII of"Saved a recording") + objdump of a byte-identical rebuild. Fix = fetch the JNIEnv inline inAndroidNotify.<audio controls>— replaces the shell-overlaid native player (a blackAVPlayerViewControlleron iOS) with a control the engine draws itself: a light bar with a ▶/⏸ button and a progress track (ckAudio+PaintAudioControl). A tap returnsTINA_AUDIO_TOGGLE; the engine APITinaAudioSrc/TinaAudioWantPlay/TinaSetAudioProgress+ shell contractAudioPlay/AudioPause/AudioProgressdrive playback. iOS/macOSAVAudioPlayer, AndroidMediaPlayer. Looks identical on every platform.Verification
test_dom✅ · compliance 219/219 ✅ · leakcheck clean ✅ ·test_interact39/39 ✅🤖 Generated with Claude Code