flutter_banuba_agora_ar 0.1.0
flutter_banuba_agora_ar: ^0.1.0 copied to clipboard
Bridge plugin that pipes Banuba AR-processed frames directly into Agora RTC as an external video source. One-liner integration for AR filters in live streams.
import 'package:flutter/material.dart';
import 'package:flutter_banuba_agora_ar/flutter_banuba_agora_ar.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Banuba + Agora Example',
theme: ThemeData(
colorSchemeSeed: Colors.teal,
useMaterial3: true,
brightness: Brightness.dark,
),
home: const BanubaAgoraExamplePage(),
);
}
}
class BanubaAgoraExamplePage extends StatefulWidget {
const BanubaAgoraExamplePage({super.key});
@override
State<BanubaAgoraExamplePage> createState() => _BanubaAgoraExamplePageState();
}
class _BanubaAgoraExamplePageState extends State<BanubaAgoraExamplePage> {
final BanubaARController _banuba = BanubaARController();
late final RtcEngine _agoraEngine;
BanubaAgoraBridge? _bridge;
bool _isInitialized = false;
bool _isStreaming = false;
String _status = 'Not initialized';
// Replace with your keys
static const String _banubaToken = 'YOUR_BANUBA_CLIENT_TOKEN';
static const String _agoraAppId = 'YOUR_AGORA_APP_ID';
static const String _channelName = 'test_channel';
@override
void initState() {
super.initState();
_initialize();
}
Future<void> _initialize() async {
setState(() => _status = 'Initializing Banuba...');
// 1. Initialize Banuba
await _banuba.initialize(token: _banubaToken);
// 2. Initialize Agora
setState(() => _status = 'Initializing Agora...');
_agoraEngine = createAgoraRtcEngine();
await _agoraEngine.initialize(RtcEngineContext(appId: _agoraAppId));
await _agoraEngine.setChannelProfile(
ChannelProfileType.channelProfileLiveBroadcasting);
await _agoraEngine.setClientRole(
role: ClientRoleType.clientRoleBroadcaster);
// 3. Enable external video source
await _agoraEngine.getMediaEngine().setExternalVideoSource(
enabled: true,
useTexture: false,
sourceType: ExternalVideoSourceType.videoFrame,
);
// 4. Create the bridge
_bridge = BanubaAgoraBridge(
banuba: _banuba,
agoraEngine: _agoraEngine,
);
setState(() {
_isInitialized = true;
_status = 'Ready — tap Start to begin streaming';
});
}
Future<void> _toggleStream() async {
if (_isStreaming) {
// Stop
_bridge?.stopForwarding();
await _banuba.stopCapture();
await _agoraEngine.leaveChannel();
setState(() {
_isStreaming = false;
_status = 'Stopped';
});
} else {
// Start
setState(() => _status = 'Starting capture...');
await _banuba.startCapture();
await _bridge?.startForwarding();
await _agoraEngine.joinChannel(
token: '',
channelId: _channelName,
uid: 0,
options: const ChannelMediaOptions(
publishCustomVideoTrack: true,
publishCameraTrack: false,
autoSubscribeVideo: true,
autoSubscribeAudio: true,
),
);
setState(() {
_isStreaming = true;
_status = 'Streaming on channel: $_channelName';
});
}
}
@override
void dispose() {
_bridge?.dispose();
_banuba.dispose();
_agoraEngine.release();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Banuba + Agora')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(_status),
if (_bridge != null) ...[
const SizedBox(height: 4),
Text('Frames sent: ${_bridge!.frameCount}',
style: Theme.of(context).textTheme.bodySmall),
],
],
),
),
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _isInitialized ? _toggleStream : null,
icon: Icon(_isStreaming ? Icons.stop : Icons.live_tv),
label: Text(_isStreaming ? 'Stop Stream' : 'Start Stream'),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: _isStreaming ? () => _bridge?.switchCamera() : null,
icon: const Icon(Icons.cameraswitch),
label: const Text('Switch Camera'),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed:
_isStreaming ? () => _bridge?.loadEffect('Beauty') : null,
icon: const Icon(Icons.auto_fix_high),
label: const Text('Load Effect'),
),
OutlinedButton.icon(
onPressed: _isStreaming ? () => _bridge?.clearEffect() : null,
icon: const Icon(Icons.clear),
label: const Text('Clear Effect'),
),
],
),
),
);
}
}