Protocol 10 presence gate, drop/paste anywhere, quick UI fixes
Ready gate: whoever is in the server while it is not playing counts (joining while playing does not pause anyone); a swap counts everyone present and keeps ready flags; the quorum rule is gone. Optional 'Unready when the video changes'. No 'Skip 1:30' guess when the media has chapters. Drag & drop and Ctrl+V anywhere (fox_utils): files, folders and links get a small card asking how to open them. Files series start collapsed. UI: Tab never moves focus and Space is always ready outside text fields; chat focuses its field; unread badge on uosc and a chip in fullscreen; people-only chat with an Activity toggle; skip pills show 5 s then with the controls; volume up to 150%; own avatar same size as the party. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f22be6c20
commit
8effa573a0
38 files changed
+1529
-303
No files matched your search
@@ -1,5 +1,5 @@
|
||||
abstract class RelayConstants {
|
||||
static const int protoVersion = 9;
|
||||
static const int protoVersion = 10;
|
||||
|
||||
// Relay (`/r/...`): a member's bytes through the server's buffer.
|
||||
static const int relaySliceBytes = 1 << 20;
|
||||
|
||||
@@ -186,12 +186,17 @@ class SyncEngine {
|
||||
return left.clamp(0.0, full).toDouble();
|
||||
}
|
||||
|
||||
/// Any frame from a member: fresh again, and a ready member the stale
|
||||
/// sweep took out of the set goes back in (ready && !inSet only arises
|
||||
/// from that sweep).
|
||||
/// Any frame from a member: fresh again. Back in the set: a ready member
|
||||
/// the stale sweep (or a swap while it was stale) took out (ready &&
|
||||
/// !inSet only arises from those), and any member back from stale while
|
||||
/// not playing (like a join then).
|
||||
void _seen(EngineMember member) {
|
||||
final wasStale = isStale(member);
|
||||
member.lastSeen = now;
|
||||
if (member.ready && !member.inSet) member.inSet = true;
|
||||
if (!member.inSet &&
|
||||
(member.ready || (wasStale && transport != Transport.playing))) {
|
||||
member.inSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Heartbeat: stale sweep, end of media.
|
||||
@@ -202,8 +207,11 @@ class SyncEngine {
|
||||
|
||||
String applyJoin(String token, String name, String avatar) {
|
||||
final id = 'm${_nextId++}';
|
||||
// Joining while not playing: in the set, so the start waits for them;
|
||||
// while playing, out until they ready (a late joiner never pauses).
|
||||
members[token] =
|
||||
EngineMember(id: id, token: token, name: name, avatar: avatar, now: now);
|
||||
EngineMember(id: id, token: token, name: name, avatar: avatar, now: now)
|
||||
..inSet = transport != Transport.playing;
|
||||
feed.add(FeedMessage(actor: name, action: 'join', at: now));
|
||||
return id;
|
||||
}
|
||||
@@ -367,9 +375,11 @@ class SyncEngine {
|
||||
serverTime = now;
|
||||
eventId++;
|
||||
transport = Transport.held;
|
||||
// Ready flags stay; everyone here now waits for everyone else here now
|
||||
// (a stale member rejoins the set with its next frame if still ready).
|
||||
final t = now;
|
||||
for (final member in members.values) {
|
||||
member.ready = false;
|
||||
member.inSet = false;
|
||||
member.inSet = !isStale(member, t);
|
||||
member.buffered = 0;
|
||||
}
|
||||
feed.add(FeedMessage(actor: actor?.name ?? '?', action: 'swap', at: now));
|
||||
@@ -448,12 +458,9 @@ class SyncEngine {
|
||||
}
|
||||
final inSet = members.values.where((m) => m.inSet).toList();
|
||||
if (inSet.isEmpty) return;
|
||||
final freshCount =
|
||||
members.values.where((m) => !isStale(m, t)).length;
|
||||
final quorum = inSet.length >= 2 || freshCount <= 1;
|
||||
final allGood = inSet.every(
|
||||
(m) => m.ready && m.buffered >= need && !isStale(m, t));
|
||||
if (allGood && quorum) {
|
||||
if (allGood) {
|
||||
serverTime = t;
|
||||
eventId++;
|
||||
transport = Transport.playing;
|
||||
|
||||
Reference in new issue
Block a user