diff options
| author | RblSb <msrblsb@gmail.com> | 2021-09-07 16:09:57 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2021-09-07 16:09:57 +0300 |
| commit | 9f819388fbcdd07b6dc85fcf87fd2d6661b61bc3 (patch) | |
| tree | 3bcd2c208a3d945bc4d859472d3bd8d66879337b | |
| parent | 64ca3915b6c9b1db931bb7737dc7a9fcef5042ac (diff) | |
Improve video skip logic
| -rw-r--r-- | build/server.js | 76 | ||||
| -rw-r--r-- | hxformat.json | 3 | ||||
| -rw-r--r-- | src/server/Main.hx | 52 | ||||
| -rw-r--r-- | test/tests/TestServer.hx | 181 |
4 files changed, 259 insertions, 53 deletions
diff --git a/build/server.js b/build/server.js index e2ada6a..e463479 100644 --- a/build/server.js +++ b/build/server.js @@ -3856,7 +3856,7 @@ server_Logger.prototype = { } ,__class__: server_Logger }; -var server_Main = function() { +var server_Main = function(opts) { this.flashbackTime = 0.0; this.loadedClientsCount = 0; this.matchGuestName = new EReg("guest [0-9]+",""); @@ -3872,6 +3872,7 @@ var server_Main = function() { this.clients = []; this.rootDir = "" + __dirname + "/.."; var _gthis = this; + this.isNoState = !opts.loadState; this.verbose = Lambda.has(process.argv.slice(2),"--verbose"); this.statePath = "" + this.rootDir + "/user/state.json"; this.logsDir = "" + this.rootDir + "/user/logs"; @@ -3903,12 +3904,12 @@ var server_Main = function() { if(envPort != null) { this.port = envPort; } - var attempts = 5; + var attempts = this.isNoState ? 500 : 5; var preparePort = null; 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 : 98, 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 : 106, className : "server.Main", methodName : "new"}); attempts -= 1; _gthis.port++; preparePort(); @@ -3921,16 +3922,18 @@ var server_Main = function() { }; server_Main.__name__ = true; server_Main.main = function() { - new server_Main(); + new server_Main({ loadState : true}); }; server_Main.prototype = { runServer: function() { var _gthis = this; - haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 111, className : "server.Main", methodName : "runServer"}); - server_Utils.getGlobalIp(function(ip) { - _gthis.globalIp = ip; - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 114, className : "server.Main", methodName : "runServer"}); - }); + haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 119, className : "server.Main", methodName : "runServer"}); + 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 : 122, className : "server.Main", methodName : "runServer"}); + }); + } var dir = "" + this.rootDir + "/res"; server_HttpServer.init(dir,"" + this.rootDir + "/user/res",this.config.localAdmins); Lang.init("" + dir + "/langs"); @@ -3997,6 +4000,9 @@ server_Main.prototype = { } ,getUserConfig: function() { var config = JSON.parse(js_node_Fs.readFileSync("" + this.rootDir + "/default-config.json",{ encoding : "utf8"})); + if(this.isNoState) { + return config; + } var customPath = "" + this.rootDir + "/user/config.json"; if(!sys_FileSystem.exists(customPath)) { return config; @@ -4008,7 +4014,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 : 182, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 191, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -4019,14 +4025,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 : 188, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 197, 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 : 192, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 201, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.image] = true; } @@ -4034,7 +4040,7 @@ server_Main.prototype = { } ,loadUsers: function() { var customPath = "" + this.rootDir + "/user/users.json"; - if(!sys_FileSystem.exists(customPath)) { + if(this.isNoState || !sys_FileSystem.exists(customPath)) { return { admins : [], bans : []}; } var users = JSON.parse(js_node_Fs.readFileSync(customPath,{ encoding : "utf8"})); @@ -4068,16 +4074,19 @@ 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 : 231, className : "server.Main", methodName : "saveState"}); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 240, className : "server.Main", methodName : "saveState"}); var json = JSON.stringify({ videoList : this.videoList, isPlaylistOpen : this.isPlaylistOpen, itemPos : this.itemPos, messages : this.messages, timer : { time : this.videoTimer.getTime(), paused : this.videoTimer.isPaused()}},null,"\t"); js_node_Fs.writeFileSync(this.statePath,json); this.writeUsers(this.userList); } ,loadState: function() { + if(this.isNoState) { + return; + } if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 249, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 259, className : "server.Main", methodName : "loadState"}); var data = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); this.videoList.length = 0; this.messages.length = 0; @@ -4094,7 +4103,7 @@ server_Main.prototype = { this.videoTimer.pause(); } ,logError: function(type,data) { - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 267, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 277, className : "server.Main", methodName : "logError", customParams : [data]}); var crashesFolder = "" + this.rootDir + "/user/crashes"; server_Utils.ensureDir(crashesFolder); js_node_Fs.writeFileSync("" + crashesFolder + "/" + (DateTools.format(new Date(),"%Y-%m-%d_%H_%M_%S") + "-" + type) + ".json",JSON.stringify(data,null,"\t")); @@ -4111,7 +4120,7 @@ server_Main.prototype = { if(_gthis.clients.length == 0) { return; } - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 284, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 294, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,null,function(r) { }); }; @@ -4132,13 +4141,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 : 308, className : "server.Main", methodName : "addAdmin"}); + haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 318, 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 : 315, className : "server.Main", methodName : "removeAdmin"}); + haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 325, className : "server.Main", methodName : "removeAdmin"}); } ,replayLog: function(events) { var _gthis = this; @@ -4182,7 +4191,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("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 353, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 363, className : "server.Main", methodName : "onConnect"}); var client = new Client(ws,req,id,name,0); client.setGroupFlag(ClientGroup.Admin,this.config.localAdmins && req.socket.localAddress == ip); this.clients.push(client); @@ -4194,7 +4203,7 @@ server_Main.prototype = { var obj = _gthis.wsEventParser.fromJson(data); 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 : 369, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 379, className : "server.Main", methodName : "onConnect"}); _gthis.serverMessage(client,errors); return; } @@ -4330,7 +4339,7 @@ server_Main.prototype = { if(!internal) { return; } - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 428, className : "server.Main", methodName : "onMessage"}); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 438, className : "server.Main", methodName : "onMessage"}); server_Utils.sortedPush(this.freeIds,client.id); HxOverrides.remove(this.clients,client); this.sendClientList(); @@ -4372,16 +4381,9 @@ server_Main.prototype = { if(this.videoTimer.getTime() > maxTime) { this.videoTimer.pause(); this.videoTimer.setTime(maxTime); - var currentLength = this.videoList.length; - var currentPos = this.itemPos; + var skipUrl = this.videoList[this.itemPos].url; haxe_Timer.delay(function() { - if(_gthis.videoList.length != currentLength) { - return; - } - if(_gthis.itemPos != currentPos) { - return; - } - _gthis.skipVideo({ type : "SkipVideo", skipVideo : { url : _gthis.videoList[_gthis.itemPos].url}}); + _gthis.skipVideo({ type : "SkipVideo", skipVideo : { url : skipUrl}}); },1000); return; } @@ -4491,7 +4493,7 @@ server_Main.prototype = { } this.videoTimer.setTime(data.pause.time); this.videoTimer.pause(); - this.broadcast(data); + this.broadcast({ type : data.type, pause : data.pause}); break; case "Play": if(this.videoList.length == 0) { @@ -4505,7 +4507,7 @@ server_Main.prototype = { } this.videoTimer.setTime(data.play.time); this.videoTimer.play(); - this.broadcast(data); + this.broadcast({ type : data.type, play : data.play}); break; case "PlayItem": if(!this.checkPermission(client,"changeOrder")) { @@ -4551,7 +4553,7 @@ server_Main.prototype = { } this.saveFlashbackTime(); this.videoTimer.setTime(data.rewind.time); - this.broadcast(data); + this.broadcast({ type : data.type, rewind : data.rewind}); break; case "ServerMessage": break; @@ -4598,7 +4600,7 @@ server_Main.prototype = { return; } this.videoTimer.setRate(data.setRate.rate); - this.broadcastExcept(client,data); + this.broadcastExcept(client,{ type : data.type, setRate : data.setRate}); break; case "SetTime": if(this.videoList.length == 0) { @@ -4611,7 +4613,7 @@ server_Main.prototype = { this.saveFlashbackTime(); } this.videoTimer.setTime(data.setTime.time); - this.broadcastExcept(client,data); + this.broadcastExcept(client,{ type : data.type, setTime : data.setTime}); break; case "ShufflePlaylist": if(!this.checkPermission(client,"changeOrder")) { @@ -4734,7 +4736,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 : 881, className : "server.Main", methodName : "checkBan"}); + haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 903, className : "server.Main", methodName : "checkBan"}); this.sendClientList(); } break; diff --git a/hxformat.json b/hxformat.json index c81c102..db7fb8f 100644 --- a/hxformat.json +++ b/hxformat.json @@ -19,6 +19,9 @@ }, "callParameter": { "defaultWrap": "keep" + }, + "methodChain": { + "rules": [] } } } diff --git a/src/server/Main.hx b/src/server/Main.hx index 49ac025..cb171ac 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -24,6 +24,10 @@ using ClientTools; using Lambda; using StringTools; +private typedef MainOptions = { + loadState:Bool +} + class Main { static inline var VIDEO_START_MAX_DELAY = 3000; static inline var VIDEO_SKIP_DELAY = 1000; @@ -34,6 +38,7 @@ class Main { public final logsDir:String; public final config:Config; + final isNoState:Bool; final verbose:Bool; final statePath:String; var wss:WSServer; @@ -53,10 +58,13 @@ class Main { var itemPos = 0; static function main():Void { - new Main(); + new Main({ + loadState: true + }); } - function new() { + function new(opts:MainOptions) { + isNoState = !opts.loadState; verbose = Sys.args().has("--verbose"); statePath = '$rootDir/user/state.json'; logsDir = '$rootDir/user/logs'; @@ -91,7 +99,7 @@ class Main { final envPort = (process.env : Dynamic).PORT; if (envPort != null) port = envPort; - var attempts = 5; + var attempts = isNoState ? 500 : 5; function preparePort():Void { Utils.isPortFree(port, isFree -> { if (!isFree && attempts > 0) { @@ -109,7 +117,7 @@ class Main { function runServer():Void { trace('Local: http://$localIp:$port'); - Utils.getGlobalIp(ip -> { + if (!isNoState) Utils.getGlobalIp(ip -> { globalIp = ip; trace('Global: http://$globalIp:$port'); }); @@ -174,6 +182,7 @@ class Main { function getUserConfig():Config { final config:Config = Json.parse(File.getContent('$rootDir/default-config.json')); + if (isNoState) return config; final customPath = '$rootDir/user/config.json'; if (!FileSystem.exists(customPath)) return config; final customConfig:Config = Json.parse(File.getContent(customPath)); @@ -198,7 +207,7 @@ class Main { function loadUsers():UserList { final customPath = '$rootDir/user/users.json'; - if (!FileSystem.exists(customPath)) return { + if (isNoState || !FileSystem.exists(customPath)) return { admins: [], bans: [] }; @@ -245,6 +254,7 @@ class Main { } function loadState():Void { + if (isNoState) return; if (!FileSystem.exists(statePath)) return; trace("Loading state..."); final data:ServerState = Json.parse(File.getContent(statePath)); @@ -621,7 +631,10 @@ class Main { } videoTimer.setTime(data.pause.time); videoTimer.pause(); - broadcast(data); + broadcast({ + type: data.type, + pause: data.pause + }); case Play: if (videoList.length == 0) return; @@ -631,7 +644,10 @@ class Main { } videoTimer.setTime(data.play.time); videoTimer.play(); - broadcast(data); + broadcast({ + type: data.type, + play: data.play + }); case GetTime: if (videoList.length == 0) return; @@ -639,15 +655,12 @@ class Main { if (videoTimer.getTime() > maxTime) { videoTimer.pause(); videoTimer.setTime(maxTime); - final currentLength = videoList.length; - final currentPos = itemPos; + final skipUrl = videoList[itemPos].url; Timer.delay(() -> { - if (videoList.length != currentLength) return; - if (itemPos != currentPos) return; skipVideo({ type: SkipVideo, skipVideo: { - url: videoList[itemPos].url + url: skipUrl } }); }, VIDEO_SKIP_DELAY); @@ -673,13 +686,19 @@ class Main { saveFlashbackTime(); } videoTimer.setTime(data.setTime.time); - broadcastExcept(client, data); + broadcastExcept(client, { + type: data.type, + setTime: data.setTime + }); case SetRate: if (videoList.length == 0) return; if (!client.isLeader) return; videoTimer.setRate(data.setRate.rate); - broadcastExcept(client, data); + broadcastExcept(client, { + type: data.type, + setRate: data.setRate + }); case Rewind: if (!checkPermission(client, RewindPerm)) return; @@ -688,7 +707,10 @@ class Main { if (data.rewind.time < 0) data.rewind.time = 0; saveFlashbackTime(); videoTimer.setTime(data.rewind.time); - broadcast(data); + broadcast({ + type: data.type, + rewind: data.rewind + }); case Flashback: if (!checkPermission(client, RewindPerm)) return; diff --git a/test/tests/TestServer.hx b/test/tests/TestServer.hx index 4fcbb2e..e5cee58 100644 --- a/test/tests/TestServer.hx +++ b/test/tests/TestServer.hx @@ -1,6 +1,12 @@ package test.tests; +import Types.WsEvent; +import Types.WsEventType; +import haxe.Json; +import haxe.Timer; +import js.lib.Promise; import js.node.Http; +import js.npm.ws.WebSocket.WebSocket; import server.Main; import utest.Assert; import utest.Async; @@ -10,7 +16,7 @@ import utest.Test; class TestServer extends Test { @:timeout(500) function testBadRequests(async:Async) { - final server = new Main(); + final server = new Main({loadState: false}); server.onServerInited = () -> { final url = 'http://${server.localIp}:${server.port}'; request('$url/你好,世界!@$^&*)_+-=', data -> { @@ -43,4 +49,177 @@ class TestServer extends Test { r.on("end", _ -> onComplete(data.toString())); }).on("error", e -> trace(e)); } + + @:timeout(2000) + function testDoubleSkip(async:Async) { + final server = new Main({loadState: false}); + server.onServerInited = () -> { + final client = new FakeClient(server.localIp, server.port); + var client2:FakeClient = null; + // client.ws.on("message", data -> { + // final data:WsEvent = Json.parse(data); + // trace(data.type); + // }); + + client.message().then((data) -> { + Assert.equals(Connected, data.type); + Assert.equals(1, server.clients.length); + client.name = data.connected.clientName; + + client2 = new FakeClient(server.localIp, server.port); + client2.message().then(data -> { + Assert.equals(Connected, data.type); + client2.name = data.connected.clientName; + }); + + client.message(); + }).then(data -> { + Assert.equals(UpdateClients, data.type); + Assert.equals(2, server.clients.length); + client.send({ + type: AddVideo, + addVideo: { + item: { + url: "url1", + title: "1", + author: "", + duration: 30, + isTemp: true, + isIframe: false + }, + atEnd: true + } + }); + }).then(data -> { + Assert.equals(AddVideo, data.type); + client2.send({type: VideoLoaded}); + client.send({type: VideoLoaded}); + }).then(data -> { + Assert.equals(VideoLoaded, data.type); + client.send({ + type: SetLeader, + setLeader: { + clientName: client.name + }, + }); + }).then(data -> { + Assert.equals(SetLeader, data.type); + client.send({ + type: SetTime, + setTime: { + time: 30 + }, + }); + // SetTime will not answer to leader request + // wait for second client data + client2.messages(2); + }).then(arr -> { + Assert.equals(SetLeader, arr[0].type); + Assert.equals(SetTime, arr[1].type); + client.send({ + type: SetLeader, + setLeader: { + clientName: "" + } + }); + }).then(data -> { + Assert.equals(SetLeader, data.type); + Timer.delay(() -> { + client2.send({type: GetTime}); + client2.send({ + type: AddVideo, + addVideo: { + item: { + url: "url2", + title: "2", + author: "", + duration: 30, + isTemp: true, + isIframe: false + }, + atEnd: true + } + }); + }, 50); + client.send({type: GetTime}); + }).then(data -> { + Assert.equals(AddVideo, data.type); + client.message(); // single skip after 1s + }).then(data -> { + Assert.equals(SkipVideo, data.type); + Assert.equals(1, server.videoList.length); + Assert.equals("url2", server.videoList[0].url); + Timer.delay(() -> { + client.send({ + type: Message, + message: { + clientName: "", + text: "ok" + } + }); + }, 50); + client.message(); + }).then(data -> { + Assert.equals(Message, data.type); + async.done(); + }); + } + } +} + +class FakeClient { + public final ws:WebSocket; + public var name = "Unknown"; + + public function new(ip:String, port:Int) { + ws = new WebSocket('ws://$ip:$port'); + } + + public function open() { + final promise = new Promise((resolve, reject) -> { + ws.once("open", _ -> resolve(_)); + ws.once("error", err -> reject(err)); + }); + return promise; + } + + // Warning: promise chain skips several fast sent messages + public function message() { + final promise = new Promise((resolve, reject) -> { + ws.once("message", data -> { + final data:WsEvent = Json.parse(data); + resolve(data); + }); + }); + return promise; + } + + public function messages(count:Int) { + var arr:Array<WsEvent> = []; + final promise = new Promise((resolve, reject) -> { + function onMessage(data:String):Void { + final data:WsEvent = Json.parse(data); + arr.push(data); + count--; + if (count == 0) { + ws.off("message", onMessage); + resolve(arr); + } + } + ws.on("message", onMessage); + }); + return promise; + } + + public function send(data:WsEvent) { + ws.send(Json.stringify(data), null); + return message(); + } + + public function wait(ms:Int) { + final promise = new Promise((resolve, reject) -> { + Timer.delay(() -> resolve(null), ms); + }); + return promise; + } } |
