feat(stories): публикация видео-историй + фикс TTL

This commit is contained in:
klockky
2026-07-24 15:31:50 +03:00
parent f7265b4fe8
commit b76da00f56
13 changed files with 723 additions and 96 deletions
@@ -0,0 +1,67 @@
import 'package:flutter/foundation.dart';
import 'persisted_setting.dart';
class AppVideoNoteResolution {
static const prefKey = 'dev_video_note_resolution';
static const int defaultValue = 480;
static const List<int> presets = [480, 720, 1080];
static final _setting = PersistedSetting<int>(
prefKey: prefKey,
defaultValue: defaultValue,
read: (prefs, key) => prefs.getInt(key),
write: (prefs, key, value) async {
await prefs.setInt(key, value);
},
sanitize: (value) => presets.contains(value) ? value : defaultValue,
);
static ValueNotifier<int> get current => _setting.current;
static Future<int> load() => _setting.load();
static Future<void> save(int value) => _setting.save(value);
}
class AppVideoNoteRearCamera {
static const prefKey = 'dev_video_note_rear_camera';
static const bool defaultValue = false;
static final _setting = PersistedSetting<bool>(
prefKey: prefKey,
defaultValue: defaultValue,
read: (prefs, key) => prefs.getBool(key),
write: (prefs, key, value) async {
await prefs.setBool(key, value);
},
);
static ValueNotifier<bool> get current => _setting.current;
static Future<bool> load() => _setting.load();
static Future<void> save(bool value) => _setting.save(value);
}
class AppVideoNoteFps {
static const prefKey = 'dev_video_note_fps';
static const int defaultValue = 30;
static const List<int> presets = [30, 60];
static final _setting = PersistedSetting<int>(
prefKey: prefKey,
defaultValue: defaultValue,
read: (prefs, key) => prefs.getInt(key),
write: (prefs, key, value) async {
await prefs.setInt(key, value);
},
sanitize: (value) => presets.contains(value) ? value : defaultValue,
);
static ValueNotifier<int> get current => _setting.current;
static Future<int> load() => _setting.load();
static Future<void> save(int value) => _setting.save(value);
}
+18 -4
View File
@@ -5,20 +5,23 @@ import 'package:flutter/services.dart';
import '../utils/logger.dart';
/// Нативная запись видео-кружка (Android, Camera2 + MediaRecorder): пишет
/// квадрат 480×480 сразу при съёмке — как официальный клиент. Превью отдаётся
/// через Flutter [Texture] по [textureId]. media3-перекод не используется
/// (серверный валидатор принимает только нативно записанный MP4).
/// квадрат сразу при съёмке — как официальный клиент (по умолчанию 480×480@30,
/// размер и fps настраиваются в дев-меню). Превью отдаётся через Flutter
/// [Texture] по [textureId]. media3-перекод не используется (серверный
/// валидатор принимает только нативно записанный MP4).
class NativeVideoNoteRecorder {
static const _channel = MethodChannel('ru.komet.app/video_note');
int? textureId;
bool get isAvailable => Platform.isAndroid;
Future<bool> init({bool front = true}) async {
Future<bool> init({bool front = true, int size = 480, int fps = 30}) async {
if (!isAvailable) return false;
try {
final res = await _channel.invokeMapMethod<String, dynamic>('init', {
'front': front,
'size': size,
'fps': fps,
});
textureId = res?['textureId'] as int?;
return textureId != null;
@@ -28,6 +31,17 @@ class NativeVideoNoteRecorder {
}
}
Future<bool> switchCamera() async {
if (!isAvailable) return false;
try {
await _channel.invokeMethod('switch');
return true;
} catch (e) {
logger.w('NativeVideoNoteRecorder.switchCamera: $e');
return false;
}
}
Future<bool> start() async {
if (!isAvailable) return false;
try {