start method
Start recording the widget with optional countdown
countdown - Optional countdown duration before recording starts
onTick - Optional callback called each second during countdown
Implementation
Future<void> start({
Duration? countdown,
Function(int remainingSeconds)? onTick,
}) async {
if (_isRecording) {
debugPrint('[WidgetRecorder] ⚠️ Already recording - ignoring start request');
return;
}
// Handle countdown if specified
if (countdown != null && countdown.inSeconds > 0) {
debugPrint('[WidgetRecorder] ⏳ Starting countdown: ${countdown.inSeconds} seconds');
for (int i = countdown.inSeconds; i > 0; i--) {
debugPrint('[WidgetRecorder] ⏳ └─ Countdown: $i...');
onTick?.call(i);
await Future.delayed(const Duration(seconds: 1));
}
debugPrint('[WidgetRecorder] ✅ Countdown complete - starting recording');
}
// Handle permission automatically if audio recording is enabled
if (recordAudio) {
debugPrint('[WidgetRecorder] 🎤 Audio recording enabled - checking permissions');
final hasPermission = await _handlePermission();
if (!hasPermission) {
debugPrint('[WidgetRecorder] ❌ Microphone permission denied');
_handleError('Microphone permission denied');
return;
}
debugPrint('[WidgetRecorder] ✅ Microphone permission granted');
}
_isRecording = true;
try {
debugPrint('[WidgetRecorder] 🎬 Starting recording...');
// Get output directory
final Directory dir;
if (customSavePath != null) {
debugPrint('[WidgetRecorder] 📁 Using custom save path: $customSavePath');
dir = Directory(customSavePath!);
// Create directory if it doesn't exist
if (!await dir.exists()) {
debugPrint('[WidgetRecorder] 📁 └─ Creating directory...');
await dir.create(recursive: true);
debugPrint('[WidgetRecorder] ✅ └─ Directory created');
} else {
debugPrint('[WidgetRecorder] ✅ └─ Directory exists');
}
} else {
debugPrint('[WidgetRecorder] 📁 Using temporary directory');
dir = await getTemporaryDirectory();
}
_outputPath = '${dir.path}/widget_rec_${DateTime.now().millisecondsSinceEpoch}.mp4';
debugPrint('[WidgetRecorder] 📁 Output path: $_outputPath');
final renderObject = _boundaryKey.currentContext?.findRenderObject();
if (renderObject == null) {
throw Exception('Widget not found. Ensure WidgetRecorder is built.');
}
_size = (renderObject as RenderRepaintBoundary).size;
// Round dimensions down to the nearest multiple of 16 for perfect encoding
final int validWidth = (_size!.width.toInt() ~/ 16) * 16;
final int validHeight = (_size!.height.toInt() ~/ 16) * 16;
debugPrint('[WidgetRecorder] 📐 Recording configuration:');
debugPrint('[WidgetRecorder] 📐 ├─ Resolution: ${validWidth}x$validHeight');
debugPrint('[WidgetRecorder] 📐 ├─ FPS: $_fps');
debugPrint('[WidgetRecorder] 📐 ├─ Audio: ${recordAudio ? "enabled" : "disabled"}');
if (_customBitrate != null) {
debugPrint('[WidgetRecorder] 📐 └─ Custom bitrate: ${(_customBitrate! / 1000000).toStringAsFixed(1)} Mbps');
}
await _channel.invokeMethod('startRecording', {
'width': validWidth,
'height': validHeight,
'fps': _fps,
'outputPath': _outputPath,
'recordAudio': recordAudio,
if (_customBitrate != null) 'bitrate': _customBitrate,
});
_timer = Timer.periodic(
Duration(milliseconds: 1000 ~/ _fps),
(_) => _captureFrame(),
);
debugPrint('[WidgetRecorder] ✅ Recording started successfully');
} catch (e) {
debugPrint('[WidgetRecorder] ❌ Error starting recording: $e');
_handleError(e.toString());
}
}