refactor(watch-together): share core primitives
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import '../primitives.dart';
|
||||
import 'watch_session.dart';
|
||||
|
||||
/// Playback lifecycle phase broadcast by the host.
|
||||
@@ -146,7 +145,7 @@ class PlaybackState {
|
||||
other.anchorHostTimeMs == anchorHostTimeMs &&
|
||||
other.rate == rate &&
|
||||
other.controlMode == controlMode &&
|
||||
const ListEquality<String>().equals(other.waitingOn, waitingOn) &&
|
||||
orderedStringListsEqual(other.waitingOn, waitingOn) &&
|
||||
other.actorPeerId == actorPeerId &&
|
||||
other.actionHint == actionHint;
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
int watchTogetherSystemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
bool orderedStringListsEqual(List<String> first, List<String> second) {
|
||||
if (first.length != second.length) return false;
|
||||
for (var i = 0; i < first.length; i++) {
|
||||
if (first[i] != second[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
||||
|
||||
import '../../mpv/mpv.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../primitives.dart';
|
||||
|
||||
enum _ExpectationKind { playing, rate }
|
||||
|
||||
@@ -44,7 +45,7 @@ class _Expectation {
|
||||
class AttachedPlayer {
|
||||
AttachedPlayer({required Player player, required this._onLost, this._remoteSeek, int Function()? nowMs})
|
||||
: _player = player,
|
||||
_nowMs = nowMs ?? _systemNowMs {
|
||||
_nowMs = nowMs ?? watchTogetherSystemNowMs {
|
||||
_lastPlaying = player.state.playing;
|
||||
_lastBuffering = player.state.buffering;
|
||||
_lastRate = player.state.rate;
|
||||
@@ -59,8 +60,6 @@ class AttachedPlayer {
|
||||
);
|
||||
}
|
||||
|
||||
static int _systemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
/// How long an issued command may wait for its property event before the
|
||||
/// expectation is considered dead (covers silently-swallowed commands).
|
||||
static const int _expectationTtlMs = 3000;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../primitives.dart';
|
||||
|
||||
/// NTP-style clock-offset estimation against the session host (guest side).
|
||||
///
|
||||
@@ -12,9 +13,7 @@ import '../../utils/app_logger.dart';
|
||||
/// All time reads go through the injected [nowMs] so tests can virtualize
|
||||
/// time alongside `fakeAsync`.
|
||||
class ClockSync {
|
||||
ClockSync({required this._sendPing, int Function()? nowMs}) : _nowMs = nowMs ?? _systemNowMs;
|
||||
|
||||
static int _systemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
ClockSync({required this._sendPing, int Function()? nowMs}) : _nowMs = nowMs ?? watchTogetherSystemNowMs;
|
||||
|
||||
static const int _windowSize = 8;
|
||||
static const int _maxAcceptedRttMs = 1000;
|
||||
|
||||
@@ -4,6 +4,7 @@ import '../../utils/app_logger.dart';
|
||||
import '../models/playback_state.dart';
|
||||
import '../models/sync_message.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../primitives.dart';
|
||||
import 'attached_player.dart';
|
||||
import 'clock_sync.dart';
|
||||
|
||||
@@ -52,9 +53,7 @@ class GuestPlaybackReconciler {
|
||||
this._callbacks = const GuestReconcilerCallbacks(),
|
||||
int Function()? nowMs,
|
||||
}) : _clock = clockSync,
|
||||
_nowMs = nowMs ?? _systemNowMs;
|
||||
|
||||
static int _systemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
_nowMs = nowMs ?? watchTogetherSystemNowMs;
|
||||
|
||||
// Tuning constants.
|
||||
static const int tickMs = 500;
|
||||
@@ -238,7 +237,7 @@ class GuestPlaybackReconciler {
|
||||
_reportedPhase = state.phase;
|
||||
_callbacks.onPhaseChanged?.call(state.phase);
|
||||
}
|
||||
if (!_listEquals(state.waitingOn, _reportedWaitingOn)) {
|
||||
if (!orderedStringListsEqual(state.waitingOn, _reportedWaitingOn)) {
|
||||
_reportedWaitingOn = state.waitingOn;
|
||||
_callbacks.onWaitingOnChanged?.call(state.waitingOn);
|
||||
}
|
||||
@@ -585,12 +584,4 @@ class GuestPlaybackReconciler {
|
||||
_lastSentStatus = status;
|
||||
_sendToHost(SyncMessage.status(status, peerId: myPeerId));
|
||||
}
|
||||
|
||||
static bool _listEquals(List<String> a, List<String> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../models/playback_state.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../primitives.dart';
|
||||
import 'attached_player.dart';
|
||||
|
||||
/// Callbacks the coordinator surfaces to the provider/UI layer.
|
||||
@@ -45,9 +46,7 @@ class HostPlaybackCoordinator {
|
||||
required this._sendState,
|
||||
this._callbacks = const HostCoordinatorCallbacks(),
|
||||
int Function()? nowMs,
|
||||
}) : _nowMs = nowMs ?? _systemNowMs;
|
||||
|
||||
static int _systemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
}) : _nowMs = nowMs ?? watchTogetherSystemNowMs;
|
||||
|
||||
// Tuning constants.
|
||||
static const int stallGraceMs = 500;
|
||||
@@ -785,18 +784,10 @@ class HostPlaybackCoordinator {
|
||||
if (toPeerId == null) {
|
||||
final previousWaiting = _lastBroadcast?.waitingOn ?? const [];
|
||||
_lastBroadcast = state;
|
||||
if (!_listEquals(previousWaiting, waitingOn)) {
|
||||
if (!orderedStringListsEqual(previousWaiting, waitingOn)) {
|
||||
_callbacks.onWaitingOnChanged?.call(waitingOn);
|
||||
}
|
||||
}
|
||||
_sendState(state, toPeerId: toPeerId);
|
||||
}
|
||||
|
||||
static bool _listEquals(List<String> a, List<String> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import '../../utils/app_logger.dart';
|
||||
import '../models/playback_state.dart';
|
||||
import '../models/sync_message.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../primitives.dart';
|
||||
import 'attached_player.dart';
|
||||
import 'clock_sync.dart';
|
||||
import 'guest_playback_reconciler.dart';
|
||||
@@ -28,7 +29,7 @@ class WatchTogetherController {
|
||||
int Function()? nowMs,
|
||||
}) : _peerService = peerService,
|
||||
_session = session,
|
||||
_nowMs = nowMs ?? _systemNowMs {
|
||||
_nowMs = nowMs ?? watchTogetherSystemNowMs {
|
||||
if (session.isHost) {
|
||||
_coordinator = HostPlaybackCoordinator(
|
||||
myPeerId: peerService.myPeerId ?? '',
|
||||
@@ -65,8 +66,6 @@ class WatchTogetherController {
|
||||
_subscriptions.add(peerService.onPeerDisconnected.listen(_handlePeerDisconnected));
|
||||
}
|
||||
|
||||
static int _systemNowMs() => DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final WatchTogetherPeerService _peerService;
|
||||
final int Function() _nowMs;
|
||||
WatchSession _session;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/watch_together/primitives.dart';
|
||||
|
||||
void main() {
|
||||
test('orderedStringListsEqual preserves order and multiplicity', () {
|
||||
expect(orderedStringListsEqual(const ['a', 'b'], const ['a', 'b']), isTrue);
|
||||
expect(orderedStringListsEqual(const ['a'], const ['a', 'b']), isFalse);
|
||||
expect(orderedStringListsEqual(const ['a', 'b'], const ['b', 'a']), isFalse);
|
||||
expect(orderedStringListsEqual(const ['a', 'a'], const ['a', 'b']), isFalse);
|
||||
});
|
||||
|
||||
test('watchTogetherSystemNowMs returns wall-clock milliseconds', () {
|
||||
final before = DateTime.now().millisecondsSinceEpoch;
|
||||
final value = watchTogetherSystemNowMs();
|
||||
final after = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
expect(value, inInclusiveRange(before, after));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user