Initial commit: watch-together phases 0-4 + presence hardening, internet-tested

This commit is contained in:
foxyz committed 2026-09-12 00:13:43 +03:00
commit 427d851a5b
101 files changed
+11326

No files matched your search

@@ -0,0 +1,102 @@
import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:media_kit/media_kit.dart';
import '../sync/room_diff.dart';
class PlaybackController {
late final Player player;
bool _ready = false;
Future<void> init() async {
player = Player(
configuration: PlayerConfiguration(
bufferSize: 64 * 1024 * 1024,
vo: Platform.environment['SYNC_RELAY_VO'],
));
_ready = true;
await _pinBufferProps();
}
Future<void> _pin(String property, String value) async {
try {
final dynamic platform = player.platform;
await platform?.setProperty(property, value);
} catch (e, st) {
debugPrint('pin $property=$value failed: $e\n$st');
}
}
Future<void> _pinBufferProps() async {
await _pin('demuxer-readahead-secs', '10');
await _pin('cache-secs', '10');
final hwdec = Platform.environment['SYNC_RELAY_HWDEC'];
if (hwdec != null && hwdec.isNotEmpty) {
await _pin('hwdec', hwdec);
}
await _pin('stream-lavf-o',
'reconnect_streamed=1,reconnect_on_http_error=1,reconnect_delay_max=4');
}
Future<void> openUrl(String url) async {
if (!_ready) return;
try {
await player.open(Media(url), play: false);
} catch (e, st) {
debugPrint('open $url failed: $e\n$st');
rethrow;
}
}
double get positionSecs =>
player.state.position.inMilliseconds / 1000.0;
double get bufferedSecsAhead {
final ahead =
(player.state.buffer - player.state.position).inMilliseconds / 1000.0;
if (ahead.isNaN || ahead < 0) return 0;
return ahead;
}
double get durationSecs =>
player.state.duration.inMilliseconds / 1000.0;
Future<void> apply(List<PlayerAction> actions) async {
for (final action in actions) {
try {
switch (action) {
case SeekAction(:final position):
if ((positionSecs - position).abs() < 0.5) break;
await player.seek(Duration(milliseconds: (position * 1000).round()));
case TransportAction(:final playing):
if (playing) {
await player.play();
} else {
await player.pause();
}
case RateAction(:final rate):
await player.setRate(rate);
}
} catch (e, st) {
debugPrint('player action $action failed: $e\n$st');
}
}
}
Future<void> dispose() async {
_ready = false;
try {
await player.dispose();
} catch (_) {}
}
Future<void> stop() async {
if (!_ready) return;
try {
await player.stop();
} catch (e, st) {
debugPrint('player stop failed: $e\n$st');
}
}
}