c6f236ecf9
Device telemetry proved the app-embedded AudioRoute class never registered (not in
Capacitor.Plugins) — appending a CAPBridgedPlugin to AppDelegate.swift gets stripped/undiscovered
in release builds. The plugins that DO register (Share, Camera, Filesystem) are all npm packages
wired by cap sync. So AudioRoute is now a local plugin package (mobile/plugins/audio-route,
file: dep in mobile/package.json) with a podspec + CAPBridgedPlugin Swift — cap sync adds its pod
and Capacitor registers it like the others. load() sets the launch speaker default; setSpeaker({on})
overrides the output port. inject-audio.js no longer injects the plugin class (would duplicate);
it keeps only the AppDelegate launch default as a fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
2.2 KiB
JavaScript
35 lines
2.2 KiB
JavaScript
// Patch the freshly-generated Capacitor iOS AppDelegate so calls DEFAULT to the loudspeaker at launch
|
|
// (iOS uses the quiet earpiece otherwise). Run on Codemagic (macOS) from ios-patch.sh:
|
|
// node mobile/scripts/inject-audio.js mobile/ios/App/App/AppDelegate.swift
|
|
// TOLERANT: exits 0 and no-ops if the template doesn't match, so it can NEVER fail the build.
|
|
// NOTE: the earpiece<->speaker TOGGLE is provided by the local Capacitor plugin package mobile/plugins/
|
|
// audio-route (registered by cap sync, like @capacitor/share). This file only sets the launch default.
|
|
const fs = require('fs');
|
|
const p = process.argv[2];
|
|
if (!p || !fs.existsSync(p)) { console.log(' (AppDelegate.swift not found — audio patch skipped)'); process.exit(0); }
|
|
try {
|
|
let s = fs.readFileSync(p, 'utf8');
|
|
if (s.includes('.defaultToSpeaker')) { console.log(' audio launch-default already patched'); process.exit(0); }
|
|
const orig = s;
|
|
if (!s.includes('import AVFoundation')) {
|
|
if (s.includes('import Capacitor')) s = s.replace('import Capacitor', 'import Capacitor\nimport AVFoundation');
|
|
else if (s.includes('import UIKit')) s = s.replace('import UIKit', 'import UIKit\nimport AVFoundation');
|
|
}
|
|
const launch = [
|
|
' // Default call audio to the loudspeaker (iOS uses the quiet earpiece otherwise).',
|
|
' do {',
|
|
' let audioSession = AVAudioSession.sharedInstance()',
|
|
' try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])',
|
|
' try audioSession.setActive(true)',
|
|
' } catch { }',
|
|
'',
|
|
].join('\n');
|
|
const m = s.match(/func\s+application\([^)]*didFinishLaunchingWithOptions[^)]*\)\s*->\s*Bool\s*\{[^\n]*\n/);
|
|
if (m) { const idx = m.index + m[0].length; s = s.slice(0, idx) + launch + s.slice(idx); }
|
|
if (s !== orig && s.includes('.defaultToSpeaker')) { fs.writeFileSync(p, s); console.log(' AVAudioSession launch default-to-speaker injected'); }
|
|
else { console.log(' (AppDelegate pattern not matched — audio patch skipped)'); }
|
|
} catch (e) {
|
|
console.log(' (audio patch error, skipped: ' + (e && e.message) + ')');
|
|
}
|
|
process.exit(0);
|