voxa_deepar 0.1.2
voxa_deepar: ^0.1.2 copied to clipboard
DeepAR camera effects for VoxaRTC. Runs on the capture path, before the encoder, so the room sees the effect rather than the streamer previewing it.
example/lib/main.dart
// Camera effects on a live VoxaRTC engine.
//
// The effect runs on the capture path, before the encoder, so it is in the
// published stream — everyone in the room sees it, not just you.
//
// Two things this file cannot supply, because they are yours:
// * a DeepAR licence key, bound to this app's application id
// * a .deepar effect file in assets/
//
// See README.md.
import 'package:flutter/material.dart';
import 'package:voxa_deepar/voxa_deepar.dart';
import 'package:voxa_rtc_engine/voxa_rtc_engine.dart';
/// Your VoxaRTC app id.
const appId = 'YOUR_APP_ID';
/// Your DeepAR licence key. Keys are per-platform and bound to the
/// application id — DeepAR fails *silently* on a mismatch, so if effects do
/// nothing and nothing is logged, check this first.
const deepArKey = 'YOUR_DEEPAR_KEY';
/// An effect you dropped into assets/.
const effectAsset = 'assets/my_effect.deepar';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MaterialApp(home: EffectsDemo()));
}
class EffectsDemo extends StatefulWidget {
const EffectsDemo({super.key});
@override
State<EffectsDemo> createState() => _EffectsDemoState();
}
class _EffectsDemoState extends State<EffectsDemo> {
// Lazy initialisers, not assignments in _start(). build() runs the moment
// the widget mounts, well before the engine has finished initialising, and
// the button below reads _effects.isSupported — so assigning it after an
// await meant the first frames rendered a LateInitializationError. Both
// constructors are synchronous and cheap, so first access is safe.
late final RtcEngine _engine = createAgoraRtcEngine();
late final VoxaDeepAr _effects = VoxaDeepAr(_engine);
VideoViewController? _preview;
bool _on = false;
String _status = 'starting';
@override
void initState() {
super.initState();
_start();
}
Future<void> _start() async {
await _engine.initialize(const RtcEngineContext(appId: appId));
await _engine.enableVideo();
await _engine.startPreview();
await _effects.setLicenseKey(deepArKey);
setState(() {
_preview = VideoViewController(
rtcEngine: _engine, canvas: const VideoCanvas(uid: 0));
_status = _effects.isSupported
? 'camera on — tap to apply the effect'
: 'effects are not available on this platform';
});
}
Future<void> _toggle() async {
if (_on) {
await _effects.clear();
setState(() {
_on = false;
_status = 'effect off — raw camera';
});
return;
}
// DeepAR reads effects from disk, so the bundled asset is copied out of
// the bundle first. Null means it is not in this build.
final path = await VoxaDeepAr.stageAsset(effectAsset);
if (path == null) {
setState(() => _status = 'no effect found at $effectAsset');
return;
}
// setEffect enables on the first call, so this is the only step between
// a raw camera and a running effect.
final ok = await _effects.setEffect(path);
setState(() {
_on = ok;
_status = ok ? 'effect on — the room sees this too' : 'could not apply';
});
}
@override
void dispose() {
// The native filter outlives this engine, so an effect left enabled would
// still be attached the next time a camera starts.
_effects.clear();
_engine.release();
super.dispose();
}
@override
Widget build(BuildContext context) {
final preview = _preview;
return Scaffold(
backgroundColor: Colors.black,
body: Stack(children: [
if (preview != null)
Positioned.fill(child: AgoraVideoView(controller: preview)),
Positioned(
left: 16,
right: 16,
bottom: 48,
child: Column(mainAxisSize: MainAxisSize.min, children: [
Container(
padding: const EdgeInsets.all(10),
color: Colors.black54,
child: Text(_status,
style: const TextStyle(color: Colors.white, fontSize: 13)),
),
const SizedBox(height: 12),
ElevatedButton(
// Gate on isSupported rather than assuming: a button that always
// fails is worse than no button.
onPressed:
preview == null || !_effects.isSupported ? null : _toggle,
child: Text(_on ? 'Turn effect off' : 'Turn effect on'),
),
]),
),
]),
);
}
}