diff options
| author | RblSb <msrblsb@gmail.com> | 2021-07-04 03:59:50 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2021-07-05 17:04:25 +0300 |
| commit | ede45cea8706eb8540e466df9861c2af8ebf9c44 (patch) | |
| tree | 74a20a6f082173378f918b48b08542881f31749a | |
| parent | 265b3e1fb56bb0e5f797b3b35227a616b108a0c3 (diff) | |
Reformat
32 files changed, 464 insertions, 312 deletions
diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fc94805 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "nadako.vshaxe", + "hookyqr.beautify" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index df67501..412a615 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,13 +1,14 @@ { "configurations": [ { - "type": "node", + "type": "pwa-node", "request": "launch", "name": "Node: build and run", "program": "${workspaceFolder}/build/server.js", "args": ["--verbose"], "sourceMaps": true, "preLaunchTask": "Build all", + "killBehavior": "polite", "console": "integratedTerminal" } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 047e16b..575d45b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,24 @@ { "haxe.configurations": [ - ["build-server.hxml"] + ["build-server.hxml"] ], "search.exclude": { "build": true, "res/temp": true, "res/client.js": true - } + }, + "[haxe]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + } + }, + "[html]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "vscode.html-language-features" + }, + "[css]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "HookyQR.beautify" + }, } @@ -7,7 +7,7 @@ Default channel example: http://synctube-example.herokuapp.com/ ### New features
- Reworked layout and theme
- Multi-Language support
-- Hotkeys (`Alt-P` for global play/pause, [etc](https://github.com/RblSb/SyncTube/blob/80ec4ba1747d22ec136a95d6b35ba6289e15e8ad/src/client/Buttons.hx#L254-L258))
+- Hotkeys (`Alt-P` for global play/pause, [etc](https://github.com/RblSb/SyncTube/blob/80ec4ba1747d22ec136a95d6b35ba6289e15e8ad/src/client/Buttons.hx#L267-L271))
- Mobile view with page fullscreen
- Way to play local videos for network users (without NAT loopback feature)
- Playback rate synchronization (with leader)
diff --git a/build/server.js b/build/server.js index f23658a..8f47179 100644 --- a/build/server.js +++ b/build/server.js @@ -3428,7 +3428,7 @@ server_ConsoleInput.prototype = { } } var _g1 = 0; - while(_g1 < _g.length) haxe_Log.trace(haxe_io_Path.withoutExtension(_g[_g1++]),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 121, className : "server.ConsoleInput", methodName : "parseLine"}); + while(_g1 < _g.length) haxe_Log.trace(haxe_io_Path.withoutExtension(_g[_g1++]),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 122, className : "server.ConsoleInput", methodName : "parseLine"}); break; case "replay": server_Utils.ensureDir(this.main.logsDir); @@ -3446,7 +3446,7 @@ server_ConsoleInput.prototype = { var len = args.length; var actual = this.commands.h[command].args.length; if(len != actual) { - haxe_Log.trace("Wrong count of arguments for command \"" + command + "\" (" + len + " instead of " + actual + ")",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 132, className : "server.ConsoleInput", methodName : "isValidArgs"}); + haxe_Log.trace("Wrong count of arguments for command \"" + command + "\" (" + len + " instead of " + actual + ")",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 134, className : "server.ConsoleInput", methodName : "isValidArgs"}); return false; } return true; @@ -3476,7 +3476,7 @@ server_ConsoleInput.prototype = { var data = _g.value; list.push("" + StringTools.rpad("/" + _g.key + " " + data.args.join(" ")," ",maxLength) + " | " + data.desc); } - haxe_Log.trace("Unknown command \"" + line + "\". List:\n" + list.join("\n"),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 151, className : "server.ConsoleInput", methodName : "printHelp"}); + haxe_Log.trace("Unknown command \"" + line + "\". List:\n" + list.join("\n"),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 153, className : "server.ConsoleInput", methodName : "printHelp"}); } ,__class__: server_ConsoleInput }; @@ -3765,9 +3765,9 @@ var server_Main = function() { var attempts = 5; var preparePort = null; preparePort = function() { - server_Utils.isPortFree(_gthis.port,function(free) { - if(!free && attempts > 0) { - haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 92, className : "server.Main", methodName : "new"}); + 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 : 97, className : "server.Main", methodName : "new"}); attempts -= 1; _gthis.port++; preparePort(); @@ -3785,10 +3785,10 @@ 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 : 105, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 110, 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 : 108, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 113, className : "server.Main", methodName : "runServer"}); }); var dir = "" + this.rootDir + "/res"; server_HttpServer.init(dir,"" + this.rootDir + "/user/res",this.config.localAdmins); @@ -3865,7 +3865,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 : 170, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 179, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -3876,14 +3876,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 : 175, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 185, 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 : 178, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 189, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.image] = true; } @@ -3902,7 +3902,7 @@ server_Main.prototype = { js_node_Fs.writeFileSync("" + folder + "/users.json",JSON.stringify(users,null,"\t")); } ,saveState: function() { - haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 200, className : "server.Main", methodName : "saveState"}); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 212, 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); } @@ -3910,7 +3910,7 @@ server_Main.prototype = { if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 217, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 229, 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; @@ -3927,7 +3927,7 @@ server_Main.prototype = { this.videoTimer.pause(); } ,logError: function(type,data) { - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 231, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 247, 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")); @@ -3943,7 +3943,7 @@ server_Main.prototype = { if(_gthis.clients.length == 0) { return; } - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 246, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 262, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,null,function(r) { }); }; @@ -3957,7 +3957,7 @@ server_Main.prototype = { } this.userList.admins.push({ name : name, hash : hash}); this.writeUsers(this.userList); - haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 261, className : "server.Main", methodName : "addAdmin"}); + haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 277, className : "server.Main", methodName : "addAdmin"}); } ,replayLog: function(events) { var _gthis = this; @@ -4001,7 +4001,7 @@ server_Main.prototype = { var ip = req.connection.remoteAddress; 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 : 299, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 315, className : "server.Main", methodName : "onConnect"}); var client = new Client(ws,req,id,name,0); client.setGroupFlag(ClientGroup.Admin,this.config.localAdmins && req.connection.localAddress == ip); this.clients.push(client); @@ -4013,7 +4013,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 : 315, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 331, className : "server.Main", methodName : "onConnect"}); _gthis.serverMessage(client,errors); return; } @@ -4113,7 +4113,7 @@ server_Main.prototype = { if(!internal) { return; } - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 370, className : "server.Main", methodName : "onMessage"}); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 387, className : "server.Main", methodName : "onMessage"}); server_Utils.sortedPush(this.freeIds,client.id); HxOverrides.remove(this.clients,client); this.sendClientList(); @@ -4526,7 +4526,7 @@ server_Utils.isPortFree = function(port,callback) { }); server.once("timeout",function() { status = false; - haxe_Log.trace("Timeout (" + timeout + "ms) occurred waiting for port " + port + " to be available",{ fileName : "src/server/Utils.hx", lineNumber : 27, className : "server.Utils", methodName : "isPortFree"}); + haxe_Log.trace("Timeout (" + timeout + "ms) occurred waiting for port " + port + " to be available",{ fileName : "src/server/Utils.hx", lineNumber : 26, className : "server.Utils", methodName : "isPortFree"}); server.close(); }); server.once("listening",function() { @@ -4540,7 +4540,7 @@ server_Utils.isPortFree = function(port,callback) { }; server_Utils.getGlobalIp = function(callback) { var onError = function(e) { - haxe_Log.trace("Warning: connection error, server is local.",{ fileName : "src/server/Utils.hx", lineNumber : 40, className : "server.Utils", methodName : "getGlobalIp"}); + haxe_Log.trace("Warning: connection error, server is local.",{ fileName : "src/server/Utils.hx", lineNumber : 39, className : "server.Utils", methodName : "getGlobalIp"}); callback("127.0.0.1"); }; var url = new js_node_url_URL("https://myexternalip.com/raw"); diff --git a/hxformat.json b/hxformat.json new file mode 100644 index 0000000..c81c102 --- /dev/null +++ b/hxformat.json @@ -0,0 +1,24 @@ +{ + "indentation": { + "tabWidth": 2 + }, + "sameLine": { + "ifBody": "same", + "elseBody": "same", + "ifElse": "keep", + "comprehensionFor": "keep" + }, + "wrapping": { + "maxLineLength": 90, + "arrayMatrixWrap": "matrixWrapNoAlign", + "arrayWrap": { + "defaultWrap": "keep" + }, + "functionSignature": { + "defaultWrap": "keep" + }, + "callParameter": { + "defaultWrap": "keep" + } + } +} diff --git a/res/css/des.css b/res/css/des.css index 6877c81..e6e9145 100644 --- a/res/css/des.css +++ b/res/css/des.css @@ -1,4 +1,5 @@ @charset "utf-8"; + /* CSS Document */ @import url(https://rsms.me/inter/inter.css); @@ -19,7 +20,10 @@ html { box-sizing: border-box; } -*, *:before, *:after { + +*, +*:before, +*:after { box-sizing: inherit; } @@ -140,7 +144,7 @@ button[disabled]:hover { cursor: default; } -button > * { +button>* { line-height: 1; } @@ -174,7 +178,7 @@ textarea { color: var(--foreground); background-color: var(--background-video); border: .125rem solid; - border-color: var(--border); + border-color: var(--border); transition: border-color ease-in-out .15s; } @@ -218,14 +222,16 @@ button.danger-bg:focus { background-color: var(--error); } -.server-msg-disconnect, .server-msg-reconnect { +.server-msg-disconnect, +.server-msg-reconnect { text-align: center; } .collapse { - display :none; - visibility :hidden; + display: none; + visibility: hidden; } + .collapse.in { display: block; visibility: visible; @@ -260,7 +266,7 @@ button.danger-bg:focus { margin-right: .5rem; } -.info header > *:not(:last-child) { +.info header>*:not(:last-child) { margin-right: .5rem; } @@ -342,7 +348,7 @@ header h4 { padding-top: 0; } -.metadata > span { +.metadata>span { display: flex; align-items: center; margin-right: 1rem; @@ -358,7 +364,7 @@ header h4 { min-width: 2rem; } -#insert_template > div:first-child { +#insert_template>div:first-child { text-align: center; flex-grow: 1; } @@ -370,12 +376,13 @@ header h4 { max-width: 32rem; } -#addfromurl > *, -#customembed > * { +#addfromurl>*, +#customembed>* { margin-bottom: 1rem; } -#mediatitle, #subsurl { +#mediatitle, +#subsurl { margin-left: 2rem; flex-grow: 1; } @@ -384,8 +391,8 @@ header h4 { flex-grow: 2; } -#customembed > input, -#customembed > textarea { +#customembed>input, +#customembed>textarea { display: block; width: 100%; } @@ -452,7 +459,7 @@ footer#footer { flex-direction: column; flex-wrap: nowrap; padding: 1rem; - height: calc( 100vh - 56.25vw ); + height: calc(100vh - 56.25vw); } #chat header { @@ -486,7 +493,7 @@ footer#footer { cursor: pointer; } -.userlist_item > *:not(:last-child) { +.userlist_item>*:not(:last-child) { margin-right: .25em; } @@ -552,6 +559,7 @@ footer#footer { } /* Message buffer */ + #messagebuffer { flex-grow: 2; flex-shrink: 8; @@ -562,7 +570,7 @@ footer#footer { height: 100%; } -#messagebuffer > * { +#messagebuffer>* { margin-bottom: 1em; } @@ -601,7 +609,7 @@ footer#footer { flex-direction: row; } -#chatbox > *:not(:first-child) { +#chatbox>*:not(:first-child) { margin-left: .5rem; } @@ -614,14 +622,14 @@ footer#footer { #smileswrap { display: none; - background: rgba(0,0,0,0.7); + background: rgba(0, 0, 0, 0.7); width: 100%; height: 12rem; padding: 1rem; border-radius: 1rem; overflow-y: scroll; text-align: center; - grid-template-columns: repeat( auto-fit, minmax( 4rem, 1fr ) ); + grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr)); grid-gap: .5rem; gap: .5rem; } @@ -643,7 +651,8 @@ footer#footer { max-height: 12.5rem; } -#guestlogin, #guestpassword { +#guestlogin, +#guestpassword { display: flex; flex-direction: column; padding-top: 1rem; @@ -653,12 +662,13 @@ footer#footer { /* Guest login */ -#guestlogin label, #guestpassword label { +#guestlogin label, +#guestpassword label { display: block; margin-bottom: 1em; } -#guestpassword span > *:not(:first-child) { +#guestpassword span>*:not(:first-child) { margin-left: .5rem; } @@ -666,6 +676,16 @@ footer#footer { cursor: pointer; } +#passwordbox { + display: flex; + flex-direction: row; +} + +#guestpass { + flex-grow: 2; + width: 2rem; +} + /* * End chat */ @@ -673,19 +693,24 @@ footer#footer { /* * Scrollbar */ + html { scrollbar-color: rgba(255, 255, 255, 0.1) transparent; scrollbar-width: thin; } + ::-webkit-scrollbar { width: 5px; } + ::-webkit-scrollbar-track { background: transparent; } + ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); } + ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.2); } @@ -703,12 +728,15 @@ html { width: 100%; height: 100vh; } + body.swap { grid-template-areas: "chat gutter video"; } + .info { flex-wrap: nowrap; } + #video { grid-area: video; width: 100%; @@ -716,6 +744,7 @@ html { overflow: auto; background: var(--background-video); } + #header { display: flex; flex: 1; @@ -725,10 +754,12 @@ html { text-overflow: ellipsis; font-size: 1.953rem; } + #currenttitle { text-overflow: ellipsis; overflow: hidden; } + .gutter { grid-area: gutter; display: block; @@ -736,9 +767,11 @@ html { background-color: var(--border); transition: background-color ease-in-out .15s; } + .gutter:hover { background-color: var(--accent); } + #chat { grid-area: chat; height: 100vh; diff --git a/res/index.html b/res/index.html index c7673b1..766ec7c 100644 --- a/res/index.html +++ b/res/index.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <html> + <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> @@ -11,11 +12,12 @@ <script type="module" src="https://cdn.jsdelivr.net/npm/ionicons@5.0.0/dist/ionicons/ionicons.esm.js"></script> <script nomodule="" src="https://cdn.jsdelivr.net/npm/ionicons@5.0.0/dist/ionicons/ionicons.js"></script> </head> + <body style="grid-template-columns: 1fr 4px 300px;"> <!-- Video --> <main id="video"> <!-- Player --> - <section id="player" > + <section id="player"> <div id="ytapiplayer" class="embed-responsive embed-responsive-16by9"></div> <!-- Video info --> <div class="info"> @@ -24,10 +26,18 @@ </header> <!-- Video controls --> <span class="controls"> - <button id="togglesynch" title="${toggleVideoSync}"><ion-icon id="pause-indicator" name="play"></ion-icon></button> - <button id="mediarefresh" title="${refreshPlayer}"><ion-icon name="refresh"></ion-icon></button> - <button id="fullscreenbtn" title="${fullscreenPlayer}"><ion-icon name="expand"></ion-icon></button> - <button id="voteskip" title="${voteForSkip}"><ion-icon name="play-skip-forward"></ion-icon></button> + <button id="togglesynch" title="${toggleVideoSync}"> + <ion-icon id="pause-indicator" name="play"></ion-icon> + </button> + <button id="mediarefresh" title="${refreshPlayer}"> + <ion-icon name="refresh"></ion-icon> + </button> + <button id="fullscreenbtn" title="${fullscreenPlayer}"> + <ion-icon name="expand"></ion-icon> + </button> + <button id="voteskip" title="${voteForSkip}"> + <ion-icon name="play-skip-forward"></ion-icon> + </button> </span> </div> </section> @@ -37,21 +47,39 @@ <div class="info"> <header> <h3>${playlist}</h3> - <button id="lockplaylist" title="${playlistOpen}"><ion-icon name="lock-open"></ion-icon></button> - <button id="getplaylist" title="${retrievePlaylistLinks}"><ion-icon name="link"></ion-icon></button> + <button id="lockplaylist" title="${playlistOpen}"> + <ion-icon name="lock-open"></ion-icon> + </button> + <button id="getplaylist" title="${retrievePlaylistLinks}"> + <ion-icon name="link"></ion-icon> + </button> </header> <!-- Playlist controls --> <span class="controls"> - <button class="collapsed" id="showmediaurl" title="${addVideoFromUrl}" data-toggle="collapse" data-target="#addfromurl" aria-expanded="false"><ion-icon name="add"></ion-icon></button> - <button class="collapsed" id="showcustomembed" title="${embedCustomFrame}" data-toggle="collapse" data-target="#customembed" aria-expanded="false"><ion-icon name="code"></ion-icon></button> - <button id="shuffleplaylist" title="${shufflePlaylist}"><ion-icon name="shuffle"></ion-icon></button> - <button id="clearplaylist" title="${clearPlaylist}"><ion-icon name="close"></ion-icon></button> + <button class="collapsed" id="showmediaurl" title="${addVideoFromUrl}" data-toggle="collapse" + data-target="#addfromurl" aria-expanded="false"> + <ion-icon name="add"></ion-icon> + </button> + <button class="collapsed" id="showcustomembed" title="${embedCustomFrame}" data-toggle="collapse" + data-target="#customembed" aria-expanded="false"> + <ion-icon name="code"></ion-icon> + </button> + <button id="shuffleplaylist" title="${shufflePlaylist}"> + <ion-icon name="shuffle"></ion-icon> + </button> + <button id="clearplaylist" title="${clearPlaylist}"> + <ion-icon name="close"></ion-icon> + </button> </span> </div> <!-- Playlist metadata --> <div class="metadata"> - <span><ion-icon name="logo-youtube"></ion-icon><span id="plcount">0 ${videos}</span></span> - <span><ion-icon name="time"></ion-icon><span id="pllength">00:00</span></span> + <span> + <ion-icon name="logo-youtube"></ion-icon><span id="plcount">0 ${videos}</span> + </span> + <span> + <ion-icon name="time"></ion-icon><span id="pllength">00:00</span> + </span> </div> <!-- Add video --> <div class="collapse" id="addfromurl" aria-expanded="false"> @@ -77,7 +105,8 @@ </div> <div class="collapse" id="customembed" aria-expanded="false"> <input id="customembed-title" type="text" placeholder="${optionalTitle}"> - <textarea id="customembed-content" rows="5" placeholder="${pasteEmbedCodeAndClick} '${queueNext}' ${or} '${queueLast}'. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${customEmbedsCannotBeSynchronized}."></textarea> + <textarea id="customembed-content" rows="5" + placeholder="${pasteEmbedCodeAndClick} '${queueNext}' ${or} '${queueLast}'. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${customEmbedsCannotBeSynchronized}."></textarea> <div> <button id="ce_queue_next">${queueNext}</button> <button id="ce_queue_end">${queueLast}</button> @@ -87,15 +116,15 @@ </div> </div> <!-- Queue --> - <div id="queuefail"> - </div> + <div id="queuefail"></div> <div> <ul class="ui-sortable queue_sortable" id="queue"></ul> </div> </section> <!-- Footer --> <footer id="footer"> - <p>Powered by <a href="https://github.com/RblSb/SyncTube" target="_blank" rel="noreferrer noopener">SyncTube</a></p> + <p>Powered by <a href="https://github.com/RblSb/SyncTube" target="_blank" rel="noreferrer noopener">SyncTube</a> + </p> </footer> </main> @@ -113,7 +142,10 @@ <span> <button id="leader_btn" title="${leaderDesc}">${leader}</button> <!-- Settings button --> - <button id="showoptions" class="collapsed" data-toggle="collapse" data-target="#optionsPanel" aria-expanded="false"><ion-icon name="settings-sharp"></ion-icon></button> + <button id="showoptions" class="collapsed" data-toggle="collapse" data-target="#optionsPanel" + aria-expanded="false"> + <ion-icon name="settings-sharp"></ion-icon> + </button> </span> </div> <!-- User list --> @@ -144,7 +176,9 @@ <!-- Message input --> <div id="chatbox"> <input id="chatline" type="text" placeholder="${chatlinePlaceholder}"> - <button id="smilesbtn" title="${emotes}"><ion-icon name="happy"></ion-icon></button> + <button id="smilesbtn" title="${emotes}"> + <ion-icon name="happy"></ion-icon> + </button> </div> <div id="smileswrap"></div> <!-- Guest login --> @@ -154,10 +188,12 @@ </div> <div id="guestpassword" style="display: none;"> <label>${enterUserPassword}</label> - <span> + <div id="passwordbox"> <input id="guestpass" type="text" placeholder="${yourPassword}"> - <button id="guestpass_icon"><ion-icon name="eye"></ion-icon></button> - </span> + <button id="guestpass_icon"> + <ion-icon name="eye"></ion-icon> + </button> + </div> </div> </aside> @@ -165,4 +201,5 @@ <script src="client.js"></script> <script src="js/custom.js"></script> </body> + </html> diff --git a/src/Client.hx b/src/Client.hx index c7bc256..254e294 100644 --- a/src/Client.hx +++ b/src/Client.hx @@ -18,7 +18,6 @@ typedef ClientData = { } class Client { - #if nodejs public final ws:WebSocket; public final id:Int; @@ -33,17 +32,18 @@ class Client { #if nodejs public function new(?ws:WebSocket, ?req:IncomingMessage, ?id:Int, name:String, group:Int) { - #else - public function new(name:String, group:Int) { - #end - #if nodejs this.ws = ws; this.req = req; this.id = id; - #end this.name = name; this.group = new EnumFlags(group); } + #else + public function new(name:String, group:Int) { + this.name = name; + this.group = new EnumFlags(group); + } + #end inline function get_isUser():Bool { return group.has(User); @@ -85,5 +85,4 @@ class Client { public static function fromData(data:ClientData):Client { return new Client(data.name, data.group); } - } diff --git a/src/ClientTools.hx b/src/ClientTools.hx index e83e450..b4ac9cb 100644 --- a/src/ClientTools.hx +++ b/src/ClientTools.hx @@ -1,10 +1,9 @@ package; -import Types.Permissions; import Types.Permission; +import Types.Permissions; class ClientTools { - public static function setLeader(clients:Array<Client>, name:String):Void { for (client in clients) { if (client.name == name) client.isLeader = true; @@ -35,5 +34,4 @@ class ClientTools { if (client.isUser) return p.user.contains(permission); return p.guest.contains(permission); } - } diff --git a/src/Lang.hx b/src/Lang.hx index 7b7642d..3bd6963 100644 --- a/src/Lang.hx +++ b/src/Lang.hx @@ -2,24 +2,25 @@ package; import haxe.Json; import haxe.io.Path; + +using Lambda; + #if (sys || nodejs) import sys.io.File; #else import haxe.Http; #end -using Lambda; private typedef LangMap = Map<String, String>; class Lang { - static final langs:Map<String, LangMap> = []; static var ids = ["en", "ru"]; #if (js && !nodejs) static var lang = js.Browser.navigator.language.substr(0, 2).toLowerCase(); #end - static function request(path:String, callback:(data:String)->Void):Void { + static function request(path:String, callback:(data:String) -> Void):Void { #if (sys || nodejs) callback(File.getContent(path)); #else @@ -29,7 +30,7 @@ class Lang { #end } - public static function init(folderPath:String, ?callback:()->Void):Void { + public static function init(folderPath:String, ?callback:() -> Void):Void { #if (js && !nodejs) // Filter unused languages ids = ids.filter(id -> id == lang || id == "en"); @@ -64,5 +65,4 @@ class Lang { return text == null ? key : text; } #end - } diff --git a/src/VideoList.hx b/src/VideoList.hx index 0799cfa..ef1d113 100644 --- a/src/VideoList.hx +++ b/src/VideoList.hx @@ -7,10 +7,8 @@ import Types.VideoItem; // items:Array<VideoItem>, // itemPos:Int // } - @:forward abstract VideoList(Array<VideoItem>) from Array<VideoItem> to Array<VideoItem> { - public function new() { this = []; } @@ -68,8 +66,9 @@ abstract VideoList(Array<VideoItem>) from Array<VideoItem> to Array<VideoItem> { public function itemsByUser(client:Client):Int { var i = 0; - for (item in this) if (item.author == client.name) i++; + for (item in this) { + if (item.author == client.name) i++; + } return i; } - } diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx index 8ed259a..ee42f44 100644 --- a/src/client/Buttons.hx +++ b/src/client/Buttons.hx @@ -1,17 +1,17 @@ package client; -import js.html.ImageElement; -import haxe.Timer; -import js.html.KeyboardEvent; -import js.html.InputElement; -import js.html.Element; import client.Main.ge; -import js.Browser.window; +import haxe.Timer; import js.Browser.document; +import js.Browser.window; +import js.html.Element; +import js.html.ImageElement; +import js.html.InputElement; +import js.html.KeyboardEvent; + using StringTools; class Buttons { - static inline var CHAT_MIN_SIZE = 200; static var split:Split; static var settings:ClientSettings; @@ -59,8 +59,9 @@ class Buttons { if (!main.isAdmin()) return; var el:Element = cast e.target; if (userList == el) return; - if (!el.classList.contains("userlist_item")) + if (!el.classList.contains("userlist_item")) { el = el.parentElement; + } var name = ""; if (el.children.length == 1) { name = el.lastElementChild.innerText; @@ -126,7 +127,7 @@ class Buttons { final icon = getPlaylist.firstElementChild; icon.setAttribute("name", "checkmark"); Timer.delay(() -> { - icon.setAttribute("name", "link"); + icon.setAttribute("name", "link"); }, 2000); } final clearPlaylist = ge("#clearplaylist"); @@ -154,7 +155,8 @@ class Buttons { final mediaUrl:InputElement = cast ge("#mediaurl"); mediaUrl.oninput = () -> { final value = mediaUrl.value; - final isRawSingleVideo = value != "" && main.isRawPlayerLink(value) && main.isSingleVideoLink(value); + final isRawSingleVideo = value != "" && main.isRawPlayerLink(value) + && main.isSingleVideoLink(value); ge("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none"; if (JsApi.hasSubtitleSupport()) { ge("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; @@ -228,7 +230,7 @@ class Buttons { document.body.style.gridTemplateColumns = sizes.join(" "); } - static function saveSplitSize():Void { + static function saveSplitSize():Void { final sizes = document.body.style.gridTemplateColumns.split(" "); if (settings.isSwapped) sizes.reverse(); settings.chatSize = Std.parseFloat(sizes[sizes.length - 1]); @@ -277,11 +279,16 @@ class Buttons { if (key == Backspace) e.preventDefault(); if (!e.altKey) return; switch (key) { - case R: ge("#mediarefresh").onclick(); - case S: ge("#voteskip").onclick(); - case C: ge("#getplaylist").onclick(); - case F: ge("#fullscreenbtn").onclick(); - case L: main.toggleLeader(); + case R: + ge("#mediarefresh").onclick(); + case S: + ge("#voteskip").onclick(); + case C: + ge("#getplaylist").onclick(); + case F: + ge("#fullscreenbtn").onclick(); + case L: + main.toggleLeader(); case P: if (!main.isLeader()) { JsApi.once(SetLeader, event -> { @@ -290,7 +297,8 @@ class Buttons { }); } main.toggleLeader(); - default: return; + default: + return; } e.preventDefault(); } @@ -346,5 +354,4 @@ class Buttons { } else el.classList.remove("mobile-view"); } } - } diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx index 7f626b1..903902e 100644 --- a/src/client/IPlayer.hx +++ b/src/client/IPlayer.hx @@ -1,12 +1,12 @@ package client; -import Types.VideoDataRequest; import Types.VideoData; +import Types.VideoDataRequest; import Types.VideoItem; interface IPlayer { function isSupportedLink(url:String):Bool; - function getVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void; + function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void; function loadVideo(item:VideoItem):Void; function removeVideo():Void; function isVideoLoaded():Bool; diff --git a/src/client/InputWithHistory.hx b/src/client/InputWithHistory.hx index c4e8bf4..e7dfe83 100644 --- a/src/client/InputWithHistory.hx +++ b/src/client/InputWithHistory.hx @@ -1,19 +1,20 @@ package client; -import js.html.KeyboardEvent; import js.html.InputElement; +import js.html.KeyboardEvent; class InputWithHistory { - final element:InputElement; final maxItems:Int; final history:Array<String>; - final onEnter:(value:String)->Bool; + final onEnter:(value:String) -> Bool; var historyId = -1; public function new( - element:InputElement, ?history:Array<String>, maxItems:Int, - onEnter:(value:String)->Bool + element:InputElement, + ?history:Array<String>, + maxItems:Int, + onEnter:(value:String) -> Bool ) { this.element = element; if (history != null) this.history = history; @@ -65,5 +66,4 @@ class InputWithHistory { function onInput():Void { if (element.oninput != null) element.oninput(); } - } diff --git a/src/client/JsApi.hx b/src/client/JsApi.hx index a236570..4d98478 100644 --- a/src/client/JsApi.hx +++ b/src/client/JsApi.hx @@ -1,18 +1,18 @@ package client; -import Types.WsEventType; -import Types.WsEvent; import Types.VideoItem; +import Types.WsEvent; +import Types.WsEventType; import js.Browser.document; import js.Browser.window; import js.Syntax; + using StringTools; -private typedef VideoChangeFunc = (item:VideoItem)->Void; -private typedef OnceEventFunc = (event:WsEvent)->Void; +private typedef VideoChangeFunc = (item:VideoItem) -> Void; +private typedef OnceEventFunc = (event:WsEvent) -> Void; class JsApi { - static var main:Main; static var player:Player; static final subtitleFormats = []; @@ -32,7 +32,7 @@ class JsApi { } @:expose - static function addPlugin(id:String, ?onLoaded:()->Void):Void { + static function addPlugin(id:String, ?onLoaded:() -> Void):Void { addScriptToHead('/plugins/$id/index.js', () -> { final obj = { api: Syntax.plainCode("client.JsApi"), @@ -49,7 +49,7 @@ class JsApi { } @:expose - public static function addScriptToHead(url:String, ?onLoaded:()->Void):Void { + public static function addScriptToHead(url:String, ?onLoaded:() -> Void):Void { final script = document.createScriptElement(); script.type = "text/javascript"; script.onload = onLoaded; @@ -176,7 +176,9 @@ class JsApi { } public static function fireVideoChangeEvents(item:VideoItem):Void { - for (func in videoChange) func(item); + for (func in videoChange) { + func(item); + } } @:expose @@ -190,7 +192,8 @@ class JsApi { } public static function fireVideoRemoveEvents(item:VideoItem):Void { - for (func in videoRemove) func(item); + for (func in videoRemove) { + func(item); + } } - } diff --git a/src/client/Main.hx b/src/client/Main.hx index 1d9ac34..e9cda39 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -1,39 +1,41 @@ package client; -import haxe.crypto.Sha256; -import haxe.Timer; -import haxe.Json; -import js.html.MouseEvent; -import js.html.KeyboardEvent; -import js.html.Event; -import js.html.Element; -import js.html.VideoElement; -import js.html.InputElement; -import js.html.ButtonElement; -import js.html.WebSocket; -import js.Browser; -import js.Browser.document; -import js.Browser.window; import Client.ClientData; -import Types.VideoDataRequest; -import Types.VideoData; +import Client.ClientGroup; import Types.Config; import Types.Permission; -import Client.ClientGroup; +import Types.VideoData; +import Types.VideoDataRequest; import Types.WsEvent; -using StringTools; +import haxe.Json; +import haxe.Timer; +import haxe.crypto.Sha256; +import js.Browser.document; +import js.Browser.window; +import js.Browser; +import js.html.ButtonElement; +import js.html.Element; +import js.html.Event; +import js.html.InputElement; +import js.html.KeyboardEvent; +import js.html.MouseEvent; +import js.html.VideoElement; +import js.html.WebSocket; + using ClientTools; +using StringTools; class Main { - static inline var SETTINGS_VERSION = 2; + public final settings:ClientSettings; public var isSyncActive = true; public var forceSyncNextTick = false; - final clients:Array<Client> = []; - var pageTitle = document.title; public final host:String; public var globalIp(default, null) = ""; + + final clients:Array<Client> = []; + var pageTitle = document.title; var config:Null<Config>; final filters:Array<{regex:EReg, replace:String}> = []; var personal = new Client("Unknown", 0); @@ -43,7 +45,9 @@ class Main { var onTimeGet:Timer; var onBlinkTab:Null<Timer>; - static function main():Void new Main(); + static function main():Void { + new Main(); + } function new() { player = new Player(this); @@ -162,8 +166,7 @@ class Main { e.preventDefault(); } } - ge("#customembed-content").onkeydown = - ge("#customembed-title").onkeydown; + ge("#customembed-content").onkeydown = ge("#customembed-title").onkeydown; } public inline function isUser():Bool { @@ -242,7 +245,7 @@ class Main { addVideo(link, atEnd, isTemp, () -> addVideoArray(links, atEnd, isTemp)); } - public function addVideo(url:String, atEnd:Bool, isTemp:Bool, ?callback:()->Void):Void { + public function addVideo(url:String, atEnd:Bool, isTemp:Bool, ?callback:() -> Void):Void { final protocol = Browser.location.protocol; if (url.startsWith("/")) { final host = Browser.location.hostname; @@ -263,7 +266,8 @@ class Main { if (data.title == null) data.title = Lang.get("rawVideo"); if (data.url == null) data.url = url; send({ - type: AddVideo, addVideo: { + type: AddVideo, + addVideo: { item: { url: data.url, title: data.title, @@ -274,7 +278,8 @@ class Main { isIframe: data.isIframe == true }, atEnd: atEnd - }}); + } + }); if (callback != null) callback(); }); } @@ -302,7 +307,8 @@ class Main { if (data.title == null) data.title = "Custom Media"; if (data.url == null) data.url = iframe; send({ - type: AddVideo, addVideo: { + type: AddVideo, + addVideo: { item: { url: data.url, title: data.title, @@ -312,13 +318,15 @@ class Main { isIframe: true }, atEnd: atEnd - }}); + } + }); }); } public function removeVideoItem(url:String) { send({ - type: RemoveVideo, removeVideo: { + type: RemoveVideo, + removeVideo: { url: url } }); @@ -343,7 +351,8 @@ class Main { public function getPlaylistLinks():Array<String> { final items = player.getItems(); return [ - for (item in items) item.url + for (item in items) + item.url ]; } @@ -556,7 +565,8 @@ class Main { public function guestLogin(name:String):Void { if (name.length == 0) return; send({ - type: Login, login: { + type: Login, + login: { clientName: name } }); @@ -576,7 +586,8 @@ class Main { public function loginRequest(name:String, hash:String):Void { send({ - type: Login, login: { + type: Login, + login: { clientName: name, passHash: hash } @@ -769,7 +780,9 @@ class Main { userDiv.appendChild(textDiv); msgBuf.appendChild(userDiv); if (isInChatEnd) { - while (msgBuf.children.length > 200) msgBuf.removeChild(msgBuf.firstChild); + while (msgBuf.children.length > 200) { + msgBuf.removeChild(msgBuf.firstChild); + } msgBuf.scrollTop = msgBuf.scrollHeight; } if (name == personal.name) { @@ -813,9 +826,12 @@ class Main { if (isAdmin()) send({type: ClearChat}); } if (matchNumbers.match(text)) { - send({type: Rewind, rewind: { - time: Std.parseInt(text) - }}); + send({ + type: Rewind, + rewind: { + time: Std.parseInt(text) + } + }); } } @@ -824,9 +840,11 @@ class Main { if (onBlinkTab != null) onBlinkTab.stop(); onBlinkTab = new Timer(1000); onBlinkTab.run = () -> { - if (document.title.startsWith(pageTitle)) + if (document.title.startsWith(pageTitle)) { document.title = title; - else document.title = getPageTitle(); + } else { + document.title = getPageTitle(); + } } onBlinkTab.run(); } @@ -912,5 +930,4 @@ class Main { public static inline function ge(id:String):Element { return document.querySelector(id); } - } diff --git a/src/client/Player.hx b/src/client/Player.hx index c9b379c..ac5c986 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -1,18 +1,18 @@ package client; -import js.html.Element; +import Types.VideoData; +import Types.VideoDataRequest; +import Types.VideoItem; import client.Main.ge; +import client.players.Iframe; import client.players.Raw; import client.players.Youtube; -import client.players.Iframe; -import Types.VideoDataRequest; -import Types.VideoData; -import Types.VideoItem; -using StringTools; +import js.html.Element; + using Lambda; +using StringTools; class Player { - final main:Main; final players:Array<IPlayer>; final iframePlayer:IPlayer; @@ -44,21 +44,24 @@ class Player { final i = Utils.getIndex(item.parentElement, item); if (btn.classList.contains("qbtn-play")) { main.send({ - type: PlayItem, playItem: { + type: PlayItem, + playItem: { pos: i } }); } if (btn.classList.contains("qbtn-next")) { main.send({ - type: SetNextItem, setNextItem: { + type: SetNextItem, + setNextItem: { pos: i } }); } if (btn.classList.contains("qbtn-tmp")) { main.send({ - type: ToggleItemType, toggleItemType: { + type: ToggleItemType, + toggleItemType: { pos: i } }); @@ -94,7 +97,7 @@ class Player { player = newPlayer; } - public function getVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { var player = players.find(player -> player.isSupportedLink(data.url)); if (player == null) player = rawPlayer; player.getVideoData(data, callback); @@ -104,7 +107,7 @@ class Player { return !players.exists(player -> player.isSupportedLink(url)); } - public function getIframeData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + public function getIframeData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { iframePlayer.getVideoData(data, callback); } @@ -167,7 +170,8 @@ class Player { public function onPlay():Void { if (!main.isLeader()) return; main.send({ - type: Play, play: { + type: Play, + play: { time: getTime() } }); @@ -184,7 +188,8 @@ class Player { final name = event.setLeader.clientName; if (name != main.getName()) return; main.send({ - type: Pause, pause: { + type: Pause, + pause: { time: getTime() } }); @@ -195,7 +200,8 @@ class Player { } if (!main.isLeader()) return; main.send({ - type: Pause, pause: { + type: Pause, + pause: { time: getTime() } }); @@ -208,7 +214,8 @@ class Player { } if (!main.isLeader()) return; main.send({ - type: SetTime, setTime: { + type: SetTime, + setTime: { time: getTime() } }); @@ -221,7 +228,8 @@ class Player { } if (!main.isLeader()) return; main.send({ - type: SetRate, setRate: { + type: SetRate, + setRate: { rate: getPlaybackRate() } }); @@ -307,7 +315,9 @@ class Player { clearItems(); if (pos != null) itemPos = pos; if (list.length == 0) return; - for (video in list) addVideoItem(video, true); + for (video in list) { + addVideoItem(video, true); + } if (currentUrl != items[itemPos].url) setVideo(itemPos); else videoItemsEl.children[itemPos].classList.add("queue_active"); } @@ -417,5 +427,4 @@ class Player { skipSetRate = isLocal; player.setPlaybackRate(rate); } - } diff --git a/src/client/Settings.hx b/src/client/Settings.hx index 47d67fc..2090829 100644 --- a/src/client/Settings.hx +++ b/src/client/Settings.hx @@ -1,14 +1,13 @@ package client; import haxe.Json; -import js.html.Storage; import js.Browser; +import js.html.Storage; private typedef Vers = {version:Int}; -private typedef Updater = (data:Any, version:Int)->Any; +private typedef Updater = (data:Any, version:Int) -> Any; class Settings { - static var defaults:Null<Vers>; static var updater:Null<Updater>; static var storage:Storage; @@ -61,5 +60,4 @@ class Settings { if (defaults == null) throw "reset: default data is null"; write(defaults); } - } diff --git a/src/client/Utils.hx b/src/client/Utils.hx index 00a26fe..de6b16c 100644 --- a/src/client/Utils.hx +++ b/src/client/Utils.hx @@ -1,11 +1,10 @@ package client; -import js.html.Element; import js.Browser.document; import js.Browser.window; +import js.html.Element; class Utils { - public static function isTouch():Bool { return js.Syntax.code("'ontouchstart' in window"); } @@ -37,11 +36,8 @@ class Utils { public static function hasFullscreen():Bool { final doc:Dynamic = document; - return ( - document.fullscreenElement != null - || doc.mozFullScreenElement != null - || doc.webkitFullscreenElement != null - ); + return (document.fullscreenElement != null || doc.mozFullScreenElement != null + || doc.webkitFullscreenElement != null); } public static function requestFullscreen(el:Element):Bool { @@ -91,5 +87,4 @@ class Utils { document.body.removeChild(textarea); } } - } diff --git a/src/client/players/Iframe.hx b/src/client/players/Iframe.hx index ae37c94..e07f814 100644 --- a/src/client/players/Iframe.hx +++ b/src/client/players/Iframe.hx @@ -1,14 +1,13 @@ package client.players; -import js.html.Element; -import js.Browser.document; -import client.Main.ge; -import Types.VideoDataRequest; import Types.VideoData; +import Types.VideoDataRequest; import Types.VideoItem; +import client.Main.ge; +import js.Browser.document; +import js.html.Element; class Iframe implements IPlayer { - final main:Main; final player:Player; final playerEl:Element = ge("#ytapiplayer"); @@ -23,7 +22,7 @@ class Iframe implements IPlayer { return true; } - public function getVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { final iframe = document.createDivElement(); iframe.innerHTML = data.url; if (isValidIframe(iframe)) { @@ -35,8 +34,7 @@ class Iframe implements IPlayer { function isValidIframe(iframe:Element):Bool { if (iframe.children.length != 1) return false; - return (iframe.firstChild.nodeName == "IFRAME" - || iframe.firstChild.nodeName == "OBJECT"); + return (iframe.firstChild.nodeName == "IFRAME" || iframe.firstChild.nodeName == "OBJECT"); } public function loadVideo(item:VideoItem):Void { @@ -79,5 +77,4 @@ class Iframe implements IPlayer { } public function setPlaybackRate(rate:Float):Void {} - } diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index cafe147..ea51e97 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -1,19 +1,19 @@ package client.players; -import js.hlsjs.Hls; +import Types.VideoData; +import Types.VideoDataRequest; +import Types.VideoItem; +import client.Main.ge; import haxe.Timer; +import js.Browser.document; +import js.hlsjs.Hls; import js.html.Element; import js.html.InputElement; import js.html.VideoElement; -import js.Browser.document; -import client.Main.ge; -import Types.VideoDataRequest; -import Types.VideoData; -import Types.VideoItem; + using StringTools; class Raw implements IPlayer { - final main:Main; final player:Player; final playerEl:Element = ge("#ytapiplayer"); @@ -34,7 +34,7 @@ class Raw implements IPlayer { return true; } - public function getVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { final url = data.url; final decodedUrl = url.urlDecode(); @@ -77,7 +77,7 @@ class Raw implements IPlayer { if (isHls) initHlsSource(video, url); } - function loadHlsPlugin(callback:()->Void):Void { + function loadHlsPlugin(callback:() -> Void):Void { final url = "https://cdn.jsdelivr.net/npm/hls.js@latest"; JsApi.addScriptToHead(url, () -> { isHlsLoaded = true; @@ -177,5 +177,4 @@ class Raw implements IPlayer { public function setPlaybackRate(rate:Float):Void { video.playbackRate = rate; } - } diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index e63a095..d4db8ed 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -1,19 +1,19 @@ package client.players; -import haxe.Json; +import Types.VideoData; +import Types.VideoDataRequest; +import Types.VideoItem; +import client.Main.ge; import haxe.Http; -import js.html.Element; +import haxe.Json; import js.Browser.document; -import client.Main.ge; +import js.html.Element; import js.youtube.Youtube as YtInit; import js.youtube.YoutubePlayer; -import Types.VideoDataRequest; -import Types.VideoData; -import Types.VideoItem; + using StringTools; class Youtube implements IPlayer { - final matchId = ~/youtube\.com.*v=([A-z0-9_-]+)/; final matchShort = ~/youtu\.be\/([A-z0-9_-]+)/; final matchEmbed = ~/youtube\.com\/embed\/([A-z0-9_-]+)/; @@ -73,7 +73,7 @@ class Youtube implements IPlayer { return total; } - public function getVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { final url = data.url; if (apiKey == null) apiKey = main.getYoutubeApiKey(); final id = extractVideoId(url); @@ -122,7 +122,7 @@ class Youtube implements IPlayer { http.request(); } - function getPlaylistVideoData(data:VideoDataRequest, callback:(data:VideoData)->Void):Void { + function getPlaylistVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { final url = data.url; final id = extractPlaylistId(url); var maxResults = main.getYoutubePlaylistLimit(); @@ -174,7 +174,7 @@ class Youtube implements IPlayer { main.serverMessage(4, 'Error $code: $msg', false); } - function getRemoteDataFallback(url:String, callback:(data:VideoData)->Void):Void { + function getRemoteDataFallback(url:String, callback:(data:VideoData) -> Void):Void { if (!YtInit.isLoadedAPI) { YtInit.init(() -> getRemoteDataFallback(url, callback)); return; @@ -292,5 +292,4 @@ class Youtube implements IPlayer { public function setPlaybackRate(rate:Float):Void { youtube.setPlaybackRate(rate); } - } diff --git a/src/server/ConsoleInput.hx b/src/server/ConsoleInput.hx index 3c287af..9b8faf3 100644 --- a/src/server/ConsoleInput.hx +++ b/src/server/ConsoleInput.hx @@ -1,13 +1,14 @@ package server; +import haxe.Json; import haxe.extern.EitherType as Or; import haxe.io.Path; -import haxe.Json; -import sys.FileSystem; -import sys.io.File; +import js.Node.process; import js.html.Console; import js.node.Readline; -import js.Node.process; +import sys.FileSystem; +import sys.io.File; + using StringTools; private typedef CommandData = { @@ -23,7 +24,6 @@ private enum abstract Command(String) from String { } class ConsoleInput { - final main:Main; final commands:Map<Command, CommandData> = [ AddAdmin => { @@ -118,7 +118,9 @@ class ConsoleInput { Utils.ensureDir(main.logsDir); final names = FileSystem.readDirectory(main.logsDir) .filter(s -> s.endsWith(".json")); - for (name in names) trace(Path.withoutExtension(name)); + for (name in names) { + trace(Path.withoutExtension(name)); + } case Exit: main.exit(); @@ -150,5 +152,4 @@ class ConsoleInput { final desc = list.join("\n"); trace('Unknown command "$line". List:\n$desc'); } - } diff --git a/src/server/HttpServer.hx b/src/server/HttpServer.hx index 46540a3..64962dc 100644 --- a/src/server/HttpServer.hx +++ b/src/server/HttpServer.hx @@ -1,20 +1,20 @@ package server; -import sys.FileSystem; -import js.node.Buffer; import haxe.io.Path; +import js.node.Buffer; import js.node.Fs; -import js.node.Https; import js.node.Http; -import js.node.url.URL; +import js.node.Https; +import js.node.Path as JsPath; +import js.node.http.ClientRequest; import js.node.http.IncomingMessage; import js.node.http.ServerResponse; -import js.node.http.ClientRequest; -import js.node.Path as JsPath; +import js.node.url.URL; +import sys.FileSystem; + using StringTools; class HttpServer { - static final mimeTypes = [ "html" => "text/html", "js" => "text/javascript", @@ -58,7 +58,8 @@ class HttpServer { res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Type", getMimeType(ext)); - if (allowLocalRequests && req.connection.remoteAddress == req.connection.localAddress + if (allowLocalRequests + && req.connection.remoteAddress == req.connection.localAddress || allowedLocalFiles[url]) { if (isMediaExtension(ext)) { allowedLocalFiles[url] = true; @@ -176,7 +177,7 @@ class HttpServer { static function proxyRequest( url:String, req:IncomingMessage, res:ServerResponse, - fn:(req:IncomingMessage)->Bool + fn:(req:IncomingMessage) -> Bool ):Null<ClientRequest> { final url = try { new URL(js.Node.global.decodeURI(url)); @@ -211,5 +212,4 @@ class HttpServer { if (contentType == null) return "application/octet-stream"; return contentType; } - } diff --git a/src/server/Logger.hx b/src/server/Logger.hx index 9ff4c34..cd96d1b 100644 --- a/src/server/Logger.hx +++ b/src/server/Logger.hx @@ -1,14 +1,14 @@ package server; -import haxe.io.Path; -import sys.io.File; import haxe.Json; +import haxe.io.Path; import sys.FileSystem; -using StringTools; +import sys.io.File; + using Lambda; +using StringTools; class Logger { - final folder:String; final maxCount:Int; final verbose:Bool; @@ -65,5 +65,4 @@ class Logger { final s = '${d[0]}-${d[1]}-${d[2]} ${t[0]}:${t[1]}:${t[2]}'; return Date.fromString(s); } - } diff --git a/src/server/Main.hx b/src/server/Main.hx index d93c2e5..ea1e40b 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -1,41 +1,44 @@ package server; -import haxe.crypto.Sha256; -import sys.FileSystem; -import sys.io.File; -import haxe.Timer; -import haxe.Json; -import js.Node.process; -import js.Node.__dirname; -import js.npm.ws.Server as WSServer; -import js.npm.ws.WebSocket; -import js.node.http.IncomingMessage; -import js.node.Http; -import json2object.JsonParser; -import json2object.ErrorUtils; import Client.ClientData; import Types.Config; +import Types.Message; import Types.Permission; import Types.UserList; -import Types.Message; import Types.WsEvent; -using StringTools; +import haxe.Json; +import haxe.Timer; +import haxe.crypto.Sha256; +import js.Node.__dirname; +import js.Node.process; +import js.node.Http; +import js.node.http.IncomingMessage; +import js.npm.ws.Server as WSServer; +import js.npm.ws.WebSocket; +import json2object.ErrorUtils; +import json2object.JsonParser; +import sys.FileSystem; +import sys.io.File; + using ClientTools; using Lambda; +using StringTools; class Main { - static inline var VIDEO_START_MAX_DELAY = 3000; static inline var VIDEO_SKIP_DELAY = 1000; + final rootDir = '$__dirname/..'; + public final logsDir:String; + public final config:Config; + final verbose:Bool; final statePath:String; var wss:WSServer; final localIp:String; var globalIp:String; var port:Int; - public final config:Config; final userList:UserList; final clients:Array<Client> = []; final freeIds:Array<Int> = []; @@ -48,7 +51,9 @@ class Main { var isPlaylistOpen = true; var itemPos = 0; - static function main():Void new Main(); + static function main():Void { + new Main(); + } function new() { verbose = Sys.args().has("--verbose"); @@ -87,8 +92,8 @@ class Main { var attempts = 5; function preparePort():Void { - Utils.isPortFree(port, free -> { - if (!free && attempts > 0) { + Utils.isPortFree(port, isFree -> { + if (!isFree && attempts > 0) { trace('Warning: port $port is already in use. Changed to ${port + 1}'); attempts--; port++; @@ -138,8 +143,9 @@ class Main { } function generateConfigSalt():String { - if (userList.salt == null) + if (userList.salt == null) { userList.salt = Sha256.encode('${Math.random()}'); + } return userList.salt; } @@ -155,7 +161,9 @@ class Main { if (type == field) continue; if (group.indexOf(type) == -1) continue; group.remove(type); - for (item in getPermissions(type)) group.push(item); + for (item in getPermissions(type)) { + group.push(item); + } } } return config; @@ -167,7 +175,9 @@ class Main { if (!FileSystem.exists(customPath)) return config; final customConfig:Config = Json.parse(File.getContent(customPath)); for (field in Reflect.fields(customConfig)) { - if (Reflect.field(config, field) == null) trace('Warning: config field "$field" is unknown'); + if (Reflect.field(config, field) == null) { + trace('Warning: config field "$field" is unknown'); + } Reflect.setField(config, field, Reflect.field(customConfig, field)); } final emoteCopies:Map<String, Bool> = []; @@ -175,7 +185,9 @@ class Main { if (emoteCopies[emote.name]) trace('Warning: emote name "${emote.name}" has copy'); emoteCopies[emote.name] = true; if (!verbose) continue; - if (emoteCopies[emote.image]) trace('Warning: emote url of name "${emote.name}" has copy'); + if (emoteCopies[emote.image]) { + trace('Warning: emote url of name "${emote.name}" has copy'); + } emoteCopies[emote.image] = true; } return config; @@ -218,10 +230,14 @@ class Main { final data:ServerState = Json.parse(File.getContent(statePath)); videoList.resize(0); messages.resize(0); - for (item in data.videoList) videoList.push(item); + for (item in data.videoList) { + videoList.push(item); + } isPlaylistOpen = data.isPlaylistOpen; itemPos = data.itemPos; - for (message in data.messages) messages.push(message); + for (message in data.messages) { + messages.push(message); + } videoTimer.start(); videoTimer.setTime(data.timer.time); videoTimer.pause(); @@ -346,8 +362,9 @@ class Main { switch (data.type) { case Connected: if (!internal) return; - if (clients.length == 1 && videoList.length > 0) + if (clients.length == 1 && videoList.length > 0) { if (videoTimer.isPaused()) videoTimer.play(); + } send(client, { type: Connected, @@ -381,7 +398,8 @@ class Main { Timer.delay(() -> { if (clients.exists(i -> i.name == client.name)) return; broadcast({ - type: ServerMessage, serverMessage: { + type: ServerMessage, + serverMessage: { textId: '${client.name} has left' } }); @@ -405,8 +423,9 @@ class Main { } } else { if (userList.admins.exists( - a -> a.name.toLowerCase() == lcName && a.hash == hash - )) client.isAdmin = true; + a -> a.name.toLowerCase() == lcName && a.hash == hash)) { + client.isAdmin = true; + } else { serverMessage(client, "passwordMatchError"); send(client, {type: LoginError}); @@ -462,8 +481,7 @@ class Main { if (!isPlaylistOpen) { if (!checkPermission(client, LockPlaylistPerm)) return; } - if (config.totalVideoLimit != 0 - && videoList.length >= config.totalVideoLimit) { + if (config.totalVideoLimit != 0 && videoList.length >= config.totalVideoLimit) { serverMessage(client, "totalVideoLimitError"); return; } @@ -540,7 +558,8 @@ class Main { if (videoList.length != currentLength) return; if (itemPos != currentPos) return; skipVideo({ - type: SkipVideo, skipVideo: { + type: SkipVideo, + skipVideo: { url: videoList[itemPos].url } }); @@ -548,7 +567,8 @@ class Main { return; } final obj:WsEvent = { - type: GetTime, getTime: { + type: GetTime, + getTime: { time: videoTimer.getTime() } }; @@ -588,7 +608,8 @@ class Main { } clients.setLeader(clientName); broadcast({ - type: SetLeader, setLeader: { + type: SetLeader, + setLeader: { clientName: clientName } }); @@ -597,7 +618,8 @@ class Main { if (videoTimer.isPaused()) videoTimer.play(); videoTimer.setRate(1); broadcast({ - type: Play, play: { + type: Play, + play: { time: videoTimer.getTime() } }); @@ -643,15 +665,17 @@ class Main { broadcast({ type: UpdatePlaylist, updatePlaylist: { - videoList: videoList - }}); + videoList: videoList + } + }); case UpdatePlaylist: broadcast({ type: UpdatePlaylist, updatePlaylist: { - videoList: videoList - }}); + videoList: videoList + } + }); case TogglePlaylistLock: if (!checkPermission(client, LockPlaylistPerm)) return; @@ -691,7 +715,8 @@ class Main { function serverMessage(client:Client, textId:String):Void { send(client, { - type: ServerMessage, serverMessage: { + type: ServerMessage, + serverMessage: { textId: textId } }); @@ -703,7 +728,8 @@ class Main { function broadcast(data:WsEvent):Void { final json = Json.stringify(data); - for (client in clients) client.ws.send(json, null); + for (client in clients) + client.ws.send(json, null); } function broadcastExcept(skipped:Client, data:WsEvent):Void { @@ -726,7 +752,8 @@ class Main { function checkPermission(client:Client, perm:Permission):Bool { final state = client.hasPermission(perm, config.permissions); if (!state) send(client, { - type: ServerMessage, serverMessage: { + type: ServerMessage, + serverMessage: { textId: "accessError" } }); @@ -767,5 +794,4 @@ class Main { broadcast({type: VideoLoaded}); videoTimer.start(); } - } diff --git a/src/server/ServerState.hx b/src/server/ServerState.hx index 9c8d751..4ccdd44 100644 --- a/src/server/ServerState.hx +++ b/src/server/ServerState.hx @@ -9,7 +9,6 @@ typedef ServerState = { itemPos:Int, messages:Array<Message>, timer:{ - time:Float, - paused:Bool + time:Float, paused:Bool } } diff --git a/src/server/Utils.hx b/src/server/Utils.hx index bab6300..9aa0d66 100644 --- a/src/server/Utils.hx +++ b/src/server/Utils.hx @@ -1,18 +1,17 @@ package server; -import js.node.url.URL; -import js.node.Https; import js.node.Http; +import js.node.Https; import js.node.Os; +import js.node.url.URL; import sys.FileSystem; class Utils { - public static function ensureDir(path:String):Void { if (!FileSystem.exists(path)) FileSystem.createDirectory(path); } - public static function isPortFree(port:Int, callback:(free:Bool)->Void):Void { + public static function isPortFree(port:Int, callback:(isFree:Bool) -> Void):Void { final server = Http.createServer(); final timeout = 1000; var status = false; @@ -22,7 +21,7 @@ class Utils { status = false; server.close(); }); - server.once("timeout", function () { + server.once("timeout", function() { status = false; trace('Timeout (${timeout}ms) occurred waiting for port $port to be available'); server.close(); @@ -35,7 +34,7 @@ class Utils { server.listen(port); } - public static function getGlobalIp(callback:(ip:String)->Void):Void { + public static function getGlobalIp(callback:(ip:String) -> Void):Void { function onError(e):Void { trace("Warning: connection error, server is local."); callback("127.0.0.1"); @@ -51,8 +50,7 @@ class Utils { final data = new StringBuf(); r.on("data", chunk -> data.add(chunk)); r.on("end", _ -> callback(data.toString())); - }).on("error", onError) - .on("timeout", onError); + }).on("error", onError).on("timeout", onError); } public static function getLocalIp():String { @@ -89,5 +87,4 @@ class Utils { arr[n] = a; } } - } diff --git a/src/server/VideoTimer.hx b/src/server/VideoTimer.hx index 53d4e28..2dd6720 100644 --- a/src/server/VideoTimer.hx +++ b/src/server/VideoTimer.hx @@ -3,8 +3,8 @@ package server; import haxe.Timer.stamp; class VideoTimer { - public var isStarted(default, null) = false; + var startTime = 0.0; var pauseStartTime = 0.0; var rateStartTime = 0.0; @@ -75,5 +75,4 @@ class VideoTimer { if (rateStartTime == 0) return 0; return stamp() - rateStartTime - pauseTime(); } - } diff --git a/test/Main.hx b/test/Main.hx index d2ea286..f212eba 100644 --- a/test/Main.hx +++ b/test/Main.hx @@ -4,12 +4,10 @@ import utest.Runner; import utest.ui.Report; class Main { - static function main() { final runner = new Runner(); runner.addCases(test.tests); Report.create(runner); runner.run(); } - } diff --git a/test/tests/TestTimer.hx b/test/tests/TestTimer.hx index cbc71d2..337da3e 100644 --- a/test/tests/TestTimer.hx +++ b/test/tests/TestTimer.hx @@ -1,14 +1,13 @@ package test.tests; import haxe.PosInfos; -import utest.Assert; -import utest.Test; -import utest.Async; import haxe.Timer; import server.VideoTimer; +import utest.Assert; +import utest.Async; +import utest.Test; class TestTimer extends Test { - @:timeout(500) function testMain(async:Async) { final timer = new VideoTimer(); @@ -168,5 +167,4 @@ class TestTimer extends Test { function almostEq(a:Float, b:Float, ?p:PosInfos):Void { Assert.equals(Math.round(a * 10) / 10, Math.round(b * 10) / 10, p); } - } |
