aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2024-08-14 19:54:25 +0300
committerRblSb <msrblsb@gmail.com>2024-08-14 19:54:25 +0300
commit38cc0a1d9b4b146af7110c681389378fd26761fa (patch)
treee4a0a35e4a518508c4127fdb368f388ce130e2a0
parent097cf610e664c922c1c618b11a582bb6a1d959f8 (diff)
Seamless reconnection for first 5 seconds
-rw-r--r--README.md4
-rw-r--r--build/server.js70
-rw-r--r--res/client.js50
-rw-r--r--src/client/Main.hx39
-rw-r--r--src/import.hx1
-rw-r--r--src/server/Main.hx24
-rw-r--r--src/tools/ArrayTools.hx116
-rw-r--r--src/utils/ArrayKeyValueReverseIterator.hx19
-rw-r--r--src/utils/ArrayReverseIterator.hx19
9 files changed, 292 insertions, 50 deletions
diff --git a/README.md b/README.md
index 1976d39..6e7c30a 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,7 @@ Lightweight modern implementation and very easy way to run locally.
Default channel example: https://synctube.onrender.com/
-### New features
-- Reworked layout and theme
+### Features
- Multi-Language support
- Hotkeys (`Alt-P` for global play/pause, [etc](https://github.com/RblSb/SyncTube/blob/d3f6d4e6434527569d13f211a0eb074c5a11992e/src/client/Buttons.hx#L303-L314))
- Mobile view with page fullscreen
@@ -18,6 +17,7 @@ Default channel example: https://synctube.onrender.com/
### Supported players
- Youtube (videos, shorts, streams and playlists)
+- [Streamable](https://streamable.com)
- Raw mp4 videos and m3u8 playlists (or any other media format supported in browser)
- Iframes (without sync)
diff --git a/build/server.js b/build/server.js
index 3868c84..076051d 100644
--- a/build/server.js
+++ b/build/server.js
@@ -1,4 +1,4 @@
-// Generated by Haxe 4.3.5
+// Generated by Haxe 4.3.6
(function ($global) { "use strict";
var $estr = function() { return js_Boot.__string_rec(this,''); },$hxEnums = $hxEnums || {},$_;
function $extend(from, fields) {
@@ -4578,7 +4578,7 @@ var server_Main = function(opts) {
preparePort = function() {
server_Utils.isPortFree(_gthis.port,function(isFree) {
if(!isFree && attempts > 0) {
- haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 113, className : "server.Main", methodName : "new"});
+ haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 122, className : "server.Main", methodName : "new"});
attempts -= 1;
_gthis.port++;
preparePort();
@@ -4596,13 +4596,13 @@ server_Main.main = function() {
server_Main.prototype = {
runServer: function() {
var _gthis = this;
- haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 126, className : "server.Main", methodName : "runServer"});
+ haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 135, className : "server.Main", methodName : "runServer"});
if(this.config.localNetworkOnly) {
- haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 128, className : "server.Main", methodName : "runServer"});
+ haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 137, className : "server.Main", methodName : "runServer"});
} else if(!this.isNoState) {
server_Utils.getGlobalIp(function(ip) {
_gthis.globalIp = ip;
- haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 132, className : "server.Main", methodName : "runServer"});
+ haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 141, className : "server.Main", methodName : "runServer"});
});
}
var dir = "" + this.rootDir + "/res";
@@ -4687,7 +4687,7 @@ server_Main.prototype = {
var field = _g1[_g];
++_g;
if(Reflect.field(config,field) == null) {
- haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 201, className : "server.Main", methodName : "getUserConfig"});
+ haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 210, className : "server.Main", methodName : "getUserConfig"});
}
config[field] = Reflect.field(customConfig,field);
}
@@ -4698,14 +4698,14 @@ server_Main.prototype = {
var emote = _g1[_g];
++_g;
if(emoteCopies_h[emote.name]) {
- haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 207, className : "server.Main", methodName : "getUserConfig"});
+ haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 216, className : "server.Main", methodName : "getUserConfig"});
}
emoteCopies_h[emote.name] = true;
if(!this.verbose) {
continue;
}
if(emoteCopies_h[emote.image]) {
- haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 211, className : "server.Main", methodName : "getUserConfig"});
+ haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 220, className : "server.Main", methodName : "getUserConfig"});
}
emoteCopies_h[emote.image] = true;
}
@@ -4743,7 +4743,7 @@ server_Main.prototype = {
js_node_Fs.writeFileSync("" + folder + "/users.json",JSON.stringify({ admins : users1, bans : _g, salt : users.salt},null,"\t"));
}
,saveState: function() {
- haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 250, className : "server.Main", methodName : "saveState"});
+ haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 259, className : "server.Main", methodName : "saveState"});
var json = JSON.stringify(this.getCurrentState(),null,"\t");
js_node_Fs.writeFileSync(this.statePath,json);
this.writeUsers(this.userList);
@@ -4758,7 +4758,7 @@ server_Main.prototype = {
if(!sys_FileSystem.exists(this.statePath)) {
return;
}
- haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 273, className : "server.Main", methodName : "loadState"});
+ haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 282, className : "server.Main", methodName : "loadState"});
var state = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"}));
this.videoList.setItems(state.videoList);
this.videoList.isOpen = state.isPlaylistOpen;
@@ -4777,7 +4777,7 @@ server_Main.prototype = {
this.videoTimer.pause();
}
,logError: function(type,data) {
- haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 291, className : "server.Main", methodName : "logError", customParams : [data]});
+ haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 300, className : "server.Main", methodName : "logError", customParams : [data]});
var crashesFolder = "" + this.rootDir + "/user/crashes";
server_Utils.ensureDir(crashesFolder);
var name = DateTools.format(new Date(),"%Y-%m-%d_%H_%M_%S") + "-" + type;
@@ -4795,7 +4795,7 @@ server_Main.prototype = {
if(_gthis.clients.length == 0) {
return;
}
- haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 308, className : "server.Main", methodName : "initIntergationHandlers"});
+ haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 317, className : "server.Main", methodName : "initIntergationHandlers"});
js_node_Http.get(url,null,function(r) {
});
};
@@ -4815,13 +4815,13 @@ server_Main.prototype = {
password += this.config.salt;
var hash = haxe_crypto_Sha256.encode(password);
this.userList.admins.push({ name : name, hash : hash});
- haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 331, className : "server.Main", methodName : "addAdmin"});
+ haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 340, className : "server.Main", methodName : "addAdmin"});
}
,removeAdmin: function(name) {
HxOverrides.remove(this.userList.admins,Lambda.find(this.userList.admins,function(item) {
return item.name == name;
}));
- haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 338, className : "server.Main", methodName : "removeAdmin"});
+ haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 347, className : "server.Main", methodName : "removeAdmin"});
}
,replayLog: function(events) {
var _gthis = this;
@@ -4865,7 +4865,7 @@ server_Main.prototype = {
var ip = this.clientIp(req);
var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length;
var name = "Guest " + (id + 1);
- haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 376, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]});
+ haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 385, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]});
var isAdmin = this.config.localAdmins && req.socket.localAddress == ip;
var client = new Client(ws,req,id,name,0);
client.setGroupFlag(ClientGroup.Admin,isAdmin);
@@ -4878,7 +4878,7 @@ server_Main.prototype = {
var obj = _gthis.wsEventParser.fromJson(data.toString());
if(_gthis.wsEventParser.errors.length > 0 || _gthis.noTypeObj(obj)) {
var errors = "" + ("Wrong request for type \"" + obj.type + "\":") + "\n" + json2object_ErrorUtils.convertErrorArray(_gthis.wsEventParser.errors);
- haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 392, className : "server.Main", methodName : "onConnect"});
+ haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 401, className : "server.Main", methodName : "onConnect"});
_gthis.serverMessage(client,errors);
return;
}
@@ -5012,6 +5012,10 @@ server_Main.prototype = {
if(!internal) {
return;
}
+ var tmp = this.emptyRoomCallbackTimer;
+ if(tmp != null) {
+ tmp.stop();
+ }
if(this.clients.length == 1 && this.videoList.items.length > 0) {
if(this.videoTimer.isPaused()) {
this.videoTimer.play();
@@ -5025,7 +5029,7 @@ server_Main.prototype = {
if(!internal) {
return;
}
- haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 452, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]});
+ haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 462, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]});
server_Utils.sortedPush(this.freeIds,client.id);
HxOverrides.remove(this.clients,client);
this.sendClientList();
@@ -5035,10 +5039,20 @@ server_Main.prototype = {
}
}
if(this.clients.length == 0) {
- if(this.waitVideoStart != null) {
- this.waitVideoStart.stop();
+ var tmp = this.emptyRoomCallbackTimer;
+ if(tmp != null) {
+ tmp.stop();
}
- this.videoTimer.pause();
+ this.emptyRoomCallbackTimer = haxe_Timer.delay(function() {
+ if(_gthis.clients.length > 0) {
+ return;
+ }
+ var tmp = _gthis.waitVideoStart;
+ if(tmp != null) {
+ tmp.stop();
+ }
+ _gthis.videoTimer.pause();
+ },5000);
}
haxe_Timer.delay(function() {
if(Lambda.exists(_gthis.clients,function(i) {
@@ -5156,7 +5170,7 @@ server_Main.prototype = {
this.send(client,{ type : "LoginError"});
return;
}
- haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 538, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]});
+ haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 552, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]});
client.name = name;
client.setGroupFlag(ClientGroup.User,true);
this.checkBan(client);
@@ -5169,7 +5183,7 @@ server_Main.prototype = {
var oldName = client.name;
client.name = "Guest " + (this.clients.indexOf(client) + 1);
client.setGroupFlag(ClientGroup.User,false);
- haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 559, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]});
+ haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 573, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]});
this.send(client,{ type : data.type, logout : { oldClientName : oldName, clientName : client.name, clients : this.clientList()}});
this.sendClientListExcept(client);
break;
@@ -5484,7 +5498,7 @@ server_Main.prototype = {
client.setGroupFlag(ClientGroup.Banned,!isOutdated);
if(isOutdated) {
HxOverrides.remove(this.userList.bans,ban);
- haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 971, className : "server.Main", methodName : "checkBan"});
+ haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 985, className : "server.Main", methodName : "checkBan"});
this.sendClientList();
}
break;
@@ -5512,8 +5526,9 @@ server_Main.prototype = {
}
,restartWaitTimer: function() {
this.videoTimer.stop();
- if(this.waitVideoStart != null) {
- this.waitVideoStart.stop();
+ var tmp = this.waitVideoStart;
+ if(tmp != null) {
+ tmp.stop();
}
this.waitVideoStart = haxe_Timer.delay($bind(this,this.startVideoPlayback),3000);
}
@@ -5530,8 +5545,9 @@ server_Main.prototype = {
}
}
,startVideoPlayback: function() {
- if(this.waitVideoStart != null) {
- this.waitVideoStart.stop();
+ var tmp = this.waitVideoStart;
+ if(tmp != null) {
+ tmp.stop();
}
this.loadedClientsCount = 0;
this.broadcast({ type : "VideoLoaded"});
diff --git a/res/client.js b/res/client.js
index 80f7597..fc9bc48 100644
--- a/res/client.js
+++ b/res/client.js
@@ -1,4 +1,4 @@
-// Generated by Haxe 4.3.5
+// Generated by Haxe 4.3.6
(function ($hx_exports, $global) { "use strict";
$hx_exports["client"] = $hx_exports["client"] || {};
$hx_exports["client"]["JsApi"] = $hx_exports["client"]["JsApi"] || {};
@@ -1254,6 +1254,7 @@ var client_Main = function() {
this.matchSimpleDate = new EReg("^-?([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s?)?$","");
this.mask = new EReg("\\${([0-9]+)-([0-9]+)}","g");
this.disabledReconnection = false;
+ this.gotInitialConnection = false;
this.isConnected = false;
this.personal = new Client("Unknown",0);
this.filters = [];
@@ -1355,19 +1356,34 @@ client_Main.prototype = {
this.ws = new WebSocket("" + protocol + "//" + this.host + colonPort + path);
this.ws.onmessage = $bind(this,this.onMessage);
this.ws.onopen = function() {
+ var tmp = _gthis.disconnectNotification;
+ if(tmp != null) {
+ tmp.stop();
+ }
+ _gthis.disconnectNotification = null;
_gthis.chatMessageConnected();
+ _gthis.gotInitialConnection = true;
return _gthis.isConnected = true;
};
this.ws.onclose = function() {
- if(_gthis.isConnected) {
- _gthis.chatMessageDisconnected();
- }
_gthis.isConnected = false;
- _gthis.player.pause();
+ var notificationDelay = _gthis.gotInitialConnection ? 5000 : 0;
+ if(_gthis.disabledReconnection) {
+ notificationDelay = 0;
+ }
+ if(_gthis.disconnectNotification == null) {
+ _gthis.disconnectNotification = haxe_Timer.delay(function() {
+ if(_gthis.isConnected) {
+ return;
+ }
+ _gthis.chatMessageDisconnected();
+ _gthis.player.pause();
+ },notificationDelay);
+ }
if(_gthis.disabledReconnection) {
return;
}
- haxe_Timer.delay($bind(_gthis,_gthis.openWebSocket),2000);
+ haxe_Timer.delay($bind(_gthis,_gthis.openWebSocket),_gthis.gotInitialConnection ? 1000 : 2000);
};
}
,initListeners: function() {
@@ -1593,7 +1609,7 @@ client_Main.prototype = {
var data = JSON.parse(e.data);
if(this.config != null && this.config.isVerbose) {
var t = data.type;
- haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 404, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
+ haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 418, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
}
client_JsApi.fireOnceEvent(data);
switch(data.type) {
@@ -2029,19 +2045,35 @@ client_Main.prototype = {
this.ws.send(JSON.stringify(data));
}
,chatMessageConnected: function() {
+ var msgBuf = window.document.querySelector("#messagebuffer");
+ if(this.isLastMessageConnectionStatus()) {
+ msgBuf.removeChild(msgBuf.lastChild);
+ }
var div = window.document.createElement("div");
div.className = "server-msg-reconnect";
div.textContent = Lang.get("msgConnected");
- window.document.querySelector("#messagebuffer").appendChild(div);
+ msgBuf.appendChild(div);
this.scrollChatToEnd();
}
,chatMessageDisconnected: function() {
+ var msgBuf = window.document.querySelector("#messagebuffer");
+ if(this.isLastMessageConnectionStatus()) {
+ msgBuf.removeChild(msgBuf.lastChild);
+ }
var div = window.document.createElement("div");
div.className = "server-msg-disconnect";
div.textContent = Lang.get("msgDisconnected");
- window.document.querySelector("#messagebuffer").appendChild(div);
+ msgBuf.appendChild(div);
this.scrollChatToEnd();
}
+ ,isLastMessageConnectionStatus: function() {
+ var tmp = window.document.querySelector("#messagebuffer").lastElementChild;
+ if(tmp != null) {
+ return StringTools.startsWith(tmp.className,"server-msg");
+ } else {
+ return null;
+ }
+ }
,updateUserList: function() {
window.document.querySelector("#usercount").textContent = this.clients.length + " " + Lang.get("online");
window.document.title = this.getPageTitle();
diff --git a/src/client/Main.hx b/src/client/Main.hx
index cb74ad8..8cdd914 100644
--- a/src/client/Main.hx
+++ b/src/client/Main.hx
@@ -41,7 +41,9 @@ class Main {
final filters:Array<{regex:EReg, replace:String}> = [];
var personal = new Client("Unknown", 0);
var isConnected = false;
+ var gotInitialConnection = false;
var disabledReconnection = false;
+ var disconnectNotification:Null<Timer>;
var ws:WebSocket;
final player:Player;
var onTimeGet:Timer;
@@ -125,17 +127,29 @@ class Main {
ws = new WebSocket('$protocol//$host$colonPort$path');
ws.onmessage = onMessage;
ws.onopen = () -> {
+ disconnectNotification?.stop();
+ disconnectNotification = null;
chatMessageConnected();
+ gotInitialConnection = true;
isConnected = true;
}
+ // if initial connection refused, or server/client is offline
ws.onclose = () -> {
- // if initial connection refused
- // or server/client offline
- if (isConnected) chatMessageDisconnected();
isConnected = false;
- player.pause();
+ var notificationDelay = gotInitialConnection ? 5000 : 0;
+ if (disabledReconnection) notificationDelay = 0;
+
+ if (disconnectNotification == null) {
+ disconnectNotification = Timer.delay(() -> {
+ if (isConnected) return;
+ chatMessageDisconnected();
+ player.pause();
+ }, notificationDelay);
+ }
+
if (disabledReconnection) return;
- Timer.delay(openWebSocket, 2000);
+ final reconnectionDelay = gotInitialConnection ? 1000 : 2000;
+ Timer.delay(openWebSocket, reconnectionDelay);
}
}
@@ -803,23 +817,34 @@ class Main {
}
function chatMessageConnected():Void {
+ final msgBuf = ge("#messagebuffer");
+ if (isLastMessageConnectionStatus()) {
+ msgBuf.removeChild(msgBuf.lastChild);
+ }
final div = document.createDivElement();
div.className = "server-msg-reconnect";
div.textContent = Lang.get("msgConnected");
- final msgBuf = ge("#messagebuffer");
msgBuf.appendChild(div);
scrollChatToEnd();
}
function chatMessageDisconnected():Void {
+ final msgBuf = ge("#messagebuffer");
+ if (isLastMessageConnectionStatus()) {
+ msgBuf.removeChild(msgBuf.lastChild);
+ }
final div = document.createDivElement();
div.className = "server-msg-disconnect";
div.textContent = Lang.get("msgDisconnected");
- final msgBuf = ge("#messagebuffer");
msgBuf.appendChild(div);
scrollChatToEnd();
}
+ function isLastMessageConnectionStatus():Bool {
+ final msgBuf = ge("#messagebuffer");
+ return msgBuf.lastElementChild?.className.startsWith("server-msg");
+ }
+
public static function serverMessage(text:String, isText = true, withTimestamp = true):Void {
final div = document.createDivElement();
final time = Date.now().toString().split(" ")[1];
diff --git a/src/import.hx b/src/import.hx
index 5517d1e..75f7907 100644
--- a/src/import.hx
+++ b/src/import.hx
@@ -1,2 +1,3 @@
using Lambda;
using StringTools;
+using tools.ArrayTools;
diff --git a/src/server/Main.hx b/src/server/Main.hx
index 1560f53..2b1aafa 100644
--- a/src/server/Main.hx
+++ b/src/server/Main.hx
@@ -33,6 +33,7 @@ class Main {
static inline var VIDEO_SKIP_DELAY = 1000;
static inline var FLASHBACKS_COUNT = 50;
static inline var FLASHBACK_DIST = 30;
+ static inline var EMPTY_ROOM_CALLBACK_DELAY = 5000;
final rootDir = '$__dirname/..';
@@ -56,6 +57,14 @@ class Main {
final messages:Array<Message> = [];
final flashbacks:Array<FlashbackItem> = [];
final logger:Logger;
+ /**
+ Stop video timer after `EMPTY_ROOM_CALLBACK_DELAY` in case
+ if server loses connection to all clients for a moment.
+
+ This allows seamless reconnection without rewinds
+ to stopped server time.
+ **/
+ var emptyRoomCallbackTimer:Null<Timer>;
static function main():Void {
new Main({
@@ -426,6 +435,7 @@ class Main {
switch (data.type) {
case Connected:
if (!internal) return;
+ emptyRoomCallbackTimer?.stop();
if (clients.length == 1 && videoList.length > 0) {
if (videoTimer.isPaused()) videoTimer.play();
}
@@ -457,8 +467,12 @@ class Main {
if (videoTimer.isPaused()) videoTimer.play();
}
if (clients.length == 0) {
- if (waitVideoStart != null) waitVideoStart.stop();
- videoTimer.pause();
+ emptyRoomCallbackTimer?.stop();
+ emptyRoomCallbackTimer = Timer.delay(() -> {
+ if (clients.length > 0) return;
+ waitVideoStart?.stop();
+ videoTimer.pause();
+ }, EMPTY_ROOM_CALLBACK_DELAY);
}
Timer.delay(() -> {
if (clients.exists(i -> i.name == client.name)) return;
@@ -987,12 +1001,12 @@ class Main {
return false;
}
- var waitVideoStart:Timer;
+ var waitVideoStart:Null<Timer>;
var loadedClientsCount = 0;
function restartWaitTimer():Void {
videoTimer.stop();
- if (waitVideoStart != null) waitVideoStart.stop();
+ waitVideoStart?.stop();
waitVideoStart = Timer.delay(startVideoPlayback, VIDEO_START_MAX_DELAY);
}
@@ -1004,7 +1018,7 @@ class Main {
}
function startVideoPlayback():Void {
- if (waitVideoStart != null) waitVideoStart.stop();
+ waitVideoStart?.stop();
loadedClientsCount = 0;
broadcast({type: VideoLoaded});
videoTimer.start();
diff --git a/src/tools/ArrayTools.hx b/src/tools/ArrayTools.hx
new file mode 100644
index 0000000..63b4839
--- /dev/null
+++ b/src/tools/ArrayTools.hx
@@ -0,0 +1,116 @@
+package tools;
+
+import utils.ArrayKeyValueReverseIterator;
+import utils.ArrayReverseIterator;
+
+class ArrayTools {
+ public static function last<T>(arr:Array<T>):Null<T> {
+ return arr[arr.length - 1];
+ }
+
+ public static function min<T:Float>(arr:Array<T>, ?maxValue:T):T {
+ var min = arr[0] ?? maxValue;
+ for (value in arr) if (value < min) min = value;
+ return min;
+ }
+
+ public static function max<T:Float>(arr:Array<T>, ?minValue:T):T {
+ var max = arr[0] ?? minValue;
+ for (value in arr) if (value > max) max = value;
+ return max;
+ }
+
+ public static function indexOfMax<T:Float>(arr:Array<T>, ?minValue:T):Int {
+ if (arr.length == 0) return -1;
+ var max = arr[0] ?? minValue;
+ var maxIndex = 0;
+ for (i in 1...arr.length) {
+ if (arr[i] > max) {
+ maxIndex = i;
+ max = arr[i];
+ }
+ }
+ return maxIndex;
+ }
+
+ public static function sum<T:Float>(arr:Array<T>):T {
+ var total:T = cast 0;
+ for (value in arr) total += value;
+ return total;
+ }
+
+ public static function shuffle<T>(arr:Array<T>):Void {
+ for (i => a in arr) {
+ final n = Std.random(arr.length);
+ final b = arr[n];
+ arr[i] = b;
+ arr[n] = a;
+ }
+ }
+
+ public static inline function reversed<T>(arr:Array<T>) {
+ return new ArrayReverseIterator(arr);
+ }
+
+ /** Key-value reversed array iterator **/
+ public static inline function reversedKV<T>(arr:Array<T>) {
+ return new ArrayKeyValueReverseIterator(arr);
+ }
+
+ public static inline function findMin<T>(
+ arr:Array<T>, f:(item:T) -> Float, maxValue:Float
+ ):Null<T> {
+ var result:Null<T> = null;
+ for (item in arr) {
+ final dist = f(item);
+ if (dist > maxValue) continue;
+ maxValue = dist;
+ result = item;
+ }
+ return result;
+ }
+
+ extern overload public static inline function inlineFind<T>(it:Array<T>, f:(item:T) -> Bool):Null<T> {
+ var result:Null<T> = null;
+ for (v in it) {
+ if (f(v)) {
+ result = v;
+ break;
+ }
+ }
+ return result;
+ }
+
+ extern overload public static inline function inlineFind<T>(it:Iterable<T>, f:(item:T) -> Bool):Null<T> {
+ var result:Null<T> = null;
+ for (v in it) {
+ if (f(v)) {
+ result = v;
+ break;
+ }
+ }
+ return result;
+ }
+
+ extern overload public static inline function inlineExists<T>(it:Array<T>, f:(item:T) -> Bool):Bool {
+ var result = false;
+ for (v in it) {
+ if (f(v)) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ extern overload public static inline function inlineExists<T>(it:Iterable<T>, f:(item:T) -> Bool):Bool {
+ var result = false;
+ for (v in it) {
+ if (f(v)) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/utils/ArrayKeyValueReverseIterator.hx b/src/utils/ArrayKeyValueReverseIterator.hx
new file mode 100644
index 0000000..ad84e73
--- /dev/null
+++ b/src/utils/ArrayKeyValueReverseIterator.hx
@@ -0,0 +1,19 @@
+package utils;
+
+class ArrayKeyValueReverseIterator<T> {
+ final arr:Array<T>;
+ var i:Int;
+
+ public inline function new(arr:Array<T>) {
+ this.arr = arr;
+ this.i = this.arr.length - 1;
+ }
+
+ public inline function hasNext() {
+ return i > -1;
+ }
+
+ public inline function next() {
+ return {value: arr[i], key: i--};
+ }
+}
diff --git a/src/utils/ArrayReverseIterator.hx b/src/utils/ArrayReverseIterator.hx
new file mode 100644
index 0000000..9976c6a
--- /dev/null
+++ b/src/utils/ArrayReverseIterator.hx
@@ -0,0 +1,19 @@
+package utils;
+
+class ArrayReverseIterator<T> {
+ final arr:Array<T>;
+ var i:Int;
+
+ public inline function new(arr:Array<T>) {
+ this.arr = arr;
+ this.i = this.arr.length - 1;
+ }
+
+ public inline function hasNext() {
+ return i > -1;
+ }
+
+ public inline function next() {
+ return arr[i--];
+ }
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage