From 9c6cd2c2310d2e3ce3d1a6e3350a97e7ba0ca657 Mon Sep 17 00:00:00 2001 From: RblSb Date: Mon, 24 Feb 2020 07:58:56 +0300 Subject: Start working on user folder --- .gitignore | 6 +- README.md | 10 +- build-client.hxml | 2 +- build/client.js | 1660 --------------------------------------------- build/server.js | 170 +++-- res/client.js | 1677 ++++++++++++++++++++++++++++++++++++++++++++++ res/css/custom.css | 4 + src/Client.hx | 11 +- src/Types.hx | 3 +- src/client/Main.hx | 39 +- src/client/Player.hx | 1 + src/server/HttpServer.hx | 79 ++- src/server/Main.hx | 66 +- src/server/Utils.hx | 11 + user/README.md | 18 + 15 files changed, 1986 insertions(+), 1771 deletions(-) delete mode 100644 build/client.js create mode 100644 res/client.js create mode 100644 user/README.md diff --git a/.gitignore b/.gitignore index 5037965..807010e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /node_modules /res/temp -/config.json +/user/config.json +/user/state.json +/user/res/ +/user/logs/ +/user/errors/ diff --git a/README.md b/README.md index bbc3986..1138e8a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ ## SyncTube Synchronized video viewing with chat and other features. -Based on CyTube, but with lightweight implementation and very easy way to run locally. +Based on CyTube layout, but with lightweight implementation and very easy way to run locally. ### New features Even if some original features are not implemented yet, there is some new things: - Multi-Language - Mobile view with page fullscreen -- Updated Des theme - -TODO: - Way to play local videos for network users (without NAT loopback feature) - `/30`, `/-21`, etc to rewind video playback in seconds +- Override every front-end file you want (`user/res` folder) +- Updated Des theme ### Setup - Open `4200` and `4201` ports in your router settings @@ -18,6 +17,9 @@ TODO: - Run `node build/server.js` - Open showed "Local" link for yourself and send "Global" link to friends +### Configuration +It's just works, but you can also check [user/ folder](/user/README.md) for server settings and additional customization. + ### Development - Install Haxe 4, VSCode and vshaxe extension. - `haxelib install all` to install extern libs. diff --git a/build-client.hxml b/build-client.hxml index d8a77e3..f4eca40 100644 --- a/build-client.hxml +++ b/build-client.hxml @@ -2,4 +2,4 @@ --main client.Main -D analyzer-optimize --dce full ---js build/client.js +--js res/client.js diff --git a/build/client.js b/build/client.js deleted file mode 100644 index 4b9954b..0000000 --- a/build/client.js +++ /dev/null @@ -1,1660 +0,0 @@ -// Generated by Haxe 4.0.5 -(function ($global) { "use strict"; -var $estr = function() { return js_Boot.__string_rec(this,''); },$hxEnums = $hxEnums || {},$_; -function $extend(from, fields) { - var proto = Object.create(from); - for (var name in fields) proto[name] = fields[name]; - if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; - return proto; -} -var ClientGroup = $hxEnums["ClientGroup"] = { __ename__ : true, __constructs__ : ["User","Leader","Admin"] - ,User: {_hx_index:0,__enum__:"ClientGroup",toString:$estr} - ,Leader: {_hx_index:1,__enum__:"ClientGroup",toString:$estr} - ,Admin: {_hx_index:2,__enum__:"ClientGroup",toString:$estr} -}; -var Client = function(ws,id,name,group) { - this.name = name; - var i = group; - if(group == null) { - i = 0; - } - this.group = i; -}; -Client.__name__ = true; -Client.fromData = function(data) { - return new Client(null,null,data.name,data.group); -}; -Client.prototype = { - setGroupFlag: function(type,flag) { - if(flag) { - this.group |= 1 << type._hx_index; - } else { - this.group &= -1 - (1 << type._hx_index); - } - return flag; - } -}; -var ClientTools = function() { }; -ClientTools.__name__ = true; -ClientTools.setLeader = function(clients,name) { - var _g = 0; - while(_g < clients.length) { - var client = clients[_g]; - ++_g; - if(client.name == name) { - client.setGroupFlag(ClientGroup.Leader,true); - } else if((client.group & 2) != 0) { - client.setGroupFlag(ClientGroup.Leader,false); - } - } -}; -ClientTools.getByName = function(clients,name,def) { - var _g = 0; - while(_g < clients.length) { - var client = clients[_g]; - ++_g; - if(client.name == name) { - return client; - } - } - return def; -}; -var EReg = function(r,opt) { - this.r = new RegExp(r,opt.split("u").join("")); -}; -EReg.__name__ = true; -EReg.prototype = { - match: function(s) { - if(this.r.global) { - this.r.lastIndex = 0; - } - this.r.m = this.r.exec(s); - this.r.s = s; - return this.r.m != null; - } - ,matched: function(n) { - if(this.r.m != null && n >= 0 && n < this.r.m.length) { - return this.r.m[n]; - } else { - throw new js__$Boot_HaxeError("EReg::matched"); - } - } -}; -var HxOverrides = function() { }; -HxOverrides.__name__ = true; -HxOverrides.substr = function(s,pos,len) { - if(len == null) { - len = s.length; - } else if(len < 0) { - if(pos == 0) { - len = s.length + len; - } else { - return ""; - } - } - return s.substr(pos,len); -}; -HxOverrides.remove = function(a,obj) { - var i = a.indexOf(obj); - if(i == -1) { - return false; - } - a.splice(i,1); - return true; -}; -HxOverrides.iter = function(a) { - return { cur : 0, arr : a, hasNext : function() { - return this.cur < this.arr.length; - }, next : function() { - return this.arr[this.cur++]; - }}; -}; -var Lambda = function() { }; -Lambda.__name__ = true; -Lambda.exists = function(it,f) { - var x = $getIterator(it); - while(x.hasNext()) if(f(x.next())) { - return true; - } - return false; -}; -Lambda.find = function(it,f) { - var v = $getIterator(it); - while(v.hasNext()) { - var v1 = v.next(); - if(f(v1)) { - return v1; - } - } - return null; -}; -var haxe_ds_StringMap = function() { - this.h = { }; -}; -haxe_ds_StringMap.__name__ = true; -haxe_ds_StringMap.prototype = { - setReserved: function(key,value) { - if(this.rh == null) { - this.rh = { }; - } - this.rh["$" + key] = value; - } - ,getReserved: function(key) { - if(this.rh == null) { - return null; - } else { - return this.rh["$" + key]; - } - } -}; -var Lang = function() { }; -Lang.__name__ = true; -Lang.request = function(path,callback) { - var http = new haxe_http_HttpJs(path); - http.onData = callback; - http.request(); -}; -Lang.init = function(folderPath,callback) { - var _this = Lang.langs; - _this.h = { }; - _this.rh = null; - var count = 0; - var _g = 0; - var _g1 = Lang.ids; - while(_g < _g1.length) { - var name = [_g1[_g]]; - ++_g; - Lang.request("" + folderPath + "/" + name[0] + ".json",(function(name1) { - return function(data) { - var data1 = JSON.parse(data); - var lang = new haxe_ds_StringMap(); - var _g2 = 0; - var _g11 = Reflect.fields(data1); - while(_g2 < _g11.length) { - var key = _g11[_g2]; - ++_g2; - var v = Reflect.field(data1,key); - if(__map_reserved[key] != null) { - lang.setReserved(key,v); - } else { - lang.h[key] = v; - } - } - var id = haxe_io_Path.withoutExtension(name1[0]); - var _this1 = Lang.langs; - if(__map_reserved[id] != null) { - _this1.setReserved(id,lang); - } else { - _this1.h[id] = lang; - } - count += 1; - if(count == Lang.ids.length && callback != null) { - callback(); - } - return; - }; - })(name)); - } -}; -Lang.get = function(key) { - var key1 = Lang.lang; - var _this = Lang.langs; - if((__map_reserved[key1] != null ? _this.getReserved(key1) : _this.h[key1]) == null) { - Lang.lang = "en"; - } - var key2 = Lang.lang; - var _this1 = Lang.langs; - var _this2 = __map_reserved[key2] != null ? _this1.getReserved(key2) : _this1.h[key2]; - var text = __map_reserved[key] != null ? _this2.getReserved(key) : _this2.h[key]; - if(text == null) { - return key; - } else { - return text; - } -}; -Math.__name__ = true; -var Reflect = function() { }; -Reflect.__name__ = true; -Reflect.field = function(o,field) { - try { - return o[field]; - } catch( e ) { - return null; - } -}; -Reflect.fields = function(o) { - var a = []; - if(o != null) { - var hasOwnProperty = Object.prototype.hasOwnProperty; - for( var f in o ) { - if(f != "__id__" && f != "hx__closures__" && hasOwnProperty.call(o,f)) { - a.push(f); - } - } - } - return a; -}; -Reflect.isFunction = function(f) { - if(typeof(f) == "function") { - return !(f.__name__ || f.__ename__); - } else { - return false; - } -}; -Reflect.compareMethods = function(f1,f2) { - if(f1 == f2) { - return true; - } - if(!Reflect.isFunction(f1) || !Reflect.isFunction(f2)) { - return false; - } - if(f1.scope == f2.scope && f1.method == f2.method) { - return f1.method != null; - } else { - return false; - } -}; -var Std = function() { }; -Std.__name__ = true; -Std.string = function(s) { - return js_Boot.__string_rec(s,""); -}; -Std.parseInt = function(x) { - if(x != null) { - var _g = 0; - var _g1 = x.length; - while(_g < _g1) { - var i = _g++; - var c = x.charCodeAt(i); - if(c <= 8 || c >= 14 && c != 32 && c != 45) { - var v = parseInt(x, (x[(i + 1)]=="x" || x[(i + 1)]=="X") ? 16 : 10); - if(isNaN(v)) { - return null; - } else { - return v; - } - } - } - } - return null; -}; -var StringTools = function() { }; -StringTools.__name__ = true; -StringTools.startsWith = function(s,start) { - if(s.length >= start.length) { - return s.lastIndexOf(start,0) == 0; - } else { - return false; - } -}; -StringTools.replace = function(s,sub,by) { - return s.split(sub).join(by); -}; -var client_Buttons = function() { }; -client_Buttons.__name__ = true; -client_Buttons.init = function(main) { - client_Buttons.initChatInput(main); - var smilesBtn = window.document.querySelector("#smilesbtn"); - smilesBtn.onclick = function(e) { - smilesBtn.classList.toggle("active"); - var smilesWrap = window.document.querySelector("#smileswrap"); - if(smilesBtn.classList.contains("active")) { - return smilesWrap.style.display = "block"; - } else { - return smilesWrap.style.display = "none"; - } - }; - window.document.querySelector("#clearchatbtn").style.display = "inline-block"; - window.document.querySelector("#clearchatbtn").onclick = function(e1) { - if((main.personal.group & 4) != 0) { - main.send({ type : "ClearChat"}); - } - return; - }; - var userList = window.document.querySelector("#userlist"); - userList.onclick = function(e2) { - if((main.personal.group & 4) == 0) { - return; - } - var el = e2.target; - if(userList == el) { - return; - } - if(!el.classList.contains("userlist_item")) { - el = el.parentElement; - } - var name = ""; - if(el.children.length == 1) { - name = el.lastElementChild.innerText; - } - main.send({ type : "SetLeader", setLeader : { clientName : name}}); - return; - }; - client_Buttons.split = new Split(["#chatwrap","#videowrap"],{ sizes : [40,60], onDragEnd : function() { - return window.dispatchEvent(new Event("resize")); - }, minSize : 185, snapOffset : 0}); - var userlistToggle = window.document.querySelector("#userlisttoggle"); - userlistToggle.onclick = function(e3) { - var style = window.document.querySelector("#userlist").style; - if(style.display == "none") { - userlistToggle.classList.add("glyphicon-chevron-down"); - userlistToggle.classList.remove("glyphicon-chevron-right"); - return style.display = "block"; - } else { - userlistToggle.classList.add("glyphicon-chevron-right"); - userlistToggle.classList.remove("glyphicon-chevron-down"); - return style.display = "none"; - } - }; - window.document.querySelector("#usercount").onclick = userlistToggle.onclick; - var extendPlayer = window.document.querySelector("#extendplayer"); - extendPlayer.onclick = function(e4) { - if(extendPlayer.classList.contains("active")) { - client_Buttons.split.setSizes([40,60]); - window.document.querySelector("#userlist").style.width = "90px"; - } else { - client_Buttons.split.setSizes([20,80]); - window.document.querySelector("#userlist").style.width = "80px"; - } - extendPlayer.classList.toggle("active"); - return window.dispatchEvent(new Event("resize")); - }; - window.document.querySelector("#mediarefresh").onclick = function(e5) { - main.refreshPlayer(); - return; - }; - window.document.querySelector("#fullscreenbtn").onclick = function(e6) { - return client_Utils.toggleFullScreen(window.document.querySelector("#ytapiplayer")); - }; - var getPlaylist = window.document.querySelector("#getplaylist"); - getPlaylist.onclick = function(e7) { - client_Utils.copyToClipboard(main.getPlaylistLinks().join(",")); - var icon = getPlaylist.firstElementChild; - icon.classList.remove("glyphicon-link"); - icon.classList.add("glyphicon-ok"); - return haxe_Timer.delay(function() { - icon.classList.add("glyphicon-link"); - icon.classList.remove("glyphicon-ok"); - return; - },2000); - }; - window.document.querySelector("#clearplaylist").onclick = function(e8) { - if(!window.confirm(Lang.get("clearPlaylistConfirm"))) { - return; - } - main.send({ type : "ClearPlaylist"}); - return; - }; - window.document.querySelector("#shuffleplaylist").onclick = function(e9) { - if(!window.confirm(Lang.get("shufflePlaylistConfirm"))) { - return; - } - main.send({ type : "ShufflePlaylist"}); - return; - }; - window.document.querySelector("#showmediaurl").onclick = function(e10) { - window.document.querySelector("#showmediaurl").classList.toggle("collapsed"); - window.document.querySelector("#showmediaurl").classList.toggle("active"); - return window.document.querySelector("#addfromurl").classList.toggle("collapse"); - }; - window.onresize = client_Buttons.onVideoResize; - window.dispatchEvent(new Event("resize")); -}; -client_Buttons.onVideoResize = function() { - var height = window.document.querySelector("#ytapiplayer").offsetHeight - window.document.querySelector("#chatline").offsetHeight; - window.document.querySelector("#messagebuffer").style.height = "" + height + "px"; - window.document.querySelector("#userlist").style.height = "" + height + "px"; -}; -client_Buttons.initChatInput = function(main) { - var guestName = window.document.querySelector("#guestname"); - guestName.onkeydown = function(e) { - if(guestName.value.length == 0) { - return; - } - if(e.keyCode == 13) { - main.send({ type : "Login", login : { clientName : guestName.value}}); - } - return; - }; - var chatLine = window.document.querySelector("#chatline"); - chatLine.onkeydown = function(e1) { - switch(e1.keyCode) { - case 13: - if(chatLine.value.length == 0) { - return; - } - main.send({ type : "Message", message : { clientName : "", text : chatLine.value}}); - client_Buttons.personalHistory.push(chatLine.value); - if(client_Buttons.personalHistory.length > 50) { - client_Buttons.personalHistory.shift(); - } - client_Buttons.personalHistoryId = -1; - chatLine.value = ""; - break; - case 38: - client_Buttons.personalHistoryId--; - if(client_Buttons.personalHistoryId == -2) { - client_Buttons.personalHistoryId = client_Buttons.personalHistory.length - 1; - if(client_Buttons.personalHistoryId == -1) { - return; - } - } else if(client_Buttons.personalHistoryId == -1) { - client_Buttons.personalHistoryId++; - } - chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; - break; - case 40: - if(client_Buttons.personalHistoryId == -1) { - return; - } - client_Buttons.personalHistoryId++; - if(client_Buttons.personalHistoryId > client_Buttons.personalHistory.length - 1) { - client_Buttons.personalHistoryId = -1; - chatLine.value = ""; - return; - } - chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; - break; - } - }; -}; -var client_Main = function(host,port) { - if(port == null) { - port = 4201; - } - this.matchNumbers = new EReg("^-?[0-9]+$",""); - this.onTimeGet = new haxe_Timer(2000); - this.isConnected = false; - this.personal = new Client(null,null,"Unknown",0); - this.filters = []; - this.pageTitle = window.document.title; - this.clients = []; - var _gthis = this; - this.player = new client_Player(this); - if(host == null) { - host = window.location.hostname; - } - if(host == "") { - host = "localhost"; - } - this.initListeners(); - this.onTimeGet.run = function() { - _gthis.send({ type : "GetTime"}); - return; - }; - window.document.onvisibilitychange = function() { - if(!window.document.hidden && _gthis.onBlinkTab != null) { - window.document.title = _gthis.getPageTitle(); - _gthis.onBlinkTab.stop(); - _gthis.onBlinkTab = null; - } - return; - }; - Lang.init("langs",function() { - _gthis.openWebSocket(host,port); - return; - }); -}; -client_Main.__name__ = true; -client_Main.main = function() { - new client_Main(); -}; -client_Main.prototype = { - openWebSocket: function(host,port) { - var _gthis = this; - this.ws = new WebSocket("ws://" + host + ":" + port); - this.ws.onmessage = $bind(this,this.onMessage); - this.ws.onopen = function() { - _gthis.serverMessage(1); - return _gthis.isConnected = true; - }; - this.ws.onclose = function() { - if(_gthis.isConnected) { - _gthis.serverMessage(2); - } - _gthis.isConnected = false; - _gthis.player.pause(); - return haxe_Timer.delay(function() { - _gthis.openWebSocket(host,port); - return; - },2000); - }; - } - ,initListeners: function() { - var _gthis = this; - client_Buttons.init(this); - client_MobileView.init(); - window.document.querySelector("#leader_btn").onclick = function(e) { - _gthis.setLeaderButton((_gthis.personal.group & 2) == 0); - _gthis.send({ type : "SetLeader", setLeader : { clientName : (_gthis.personal.group & 2) != 0 ? "" : _gthis.personal.name}}); - return; - }; - window.document.querySelector("#queue_next").onclick = function(e1) { - _gthis.addVideoUrl(false); - return; - }; - window.document.querySelector("#queue_end").onclick = function(e2) { - _gthis.addVideoUrl(true); - return; - }; - window.document.querySelector("#mediaurl").onkeydown = function(e3) { - if(e3.keyCode == 13) { - _gthis.addVideoUrl(true); - } - }; - } - ,addVideoUrl: function(atEnd) { - var _gthis = this; - var mediaUrl = window.document.querySelector("#mediaurl"); - var url = mediaUrl.value; - if(url.length == 0) { - return; - } - mediaUrl.value = ""; - var _this_r = new RegExp(",(https?)","g".split("u").join("")); - var links = url.replace(_this_r,"|$1").split("|"); - this.addVideo(atEnd || this.player.isListEmpty() ? links.shift() : links.pop(),atEnd,function() { - _gthis.addVideoArray(links,atEnd); - return; - }); - } - ,addVideoArray: function(links,atEnd) { - var _gthis = this; - if(links.length == 0) { - return; - } - this.addVideo(atEnd ? links.shift() : links.pop(),atEnd,function() { - _gthis.addVideoArray(links,atEnd); - return; - }); - } - ,addVideo: function(url,atEnd,callback) { - var _gthis = this; - if(!StringTools.startsWith(url,"http")) { - url = "" + window.location.protocol + "//" + url; - } - var pos = url.lastIndexOf("/") + 1; - var name = HxOverrides.substr(url,pos,null); - var matchName = new EReg("^(.+)\\.",""); - if(matchName.match(name)) { - name = matchName.matched(1); - } else { - name = Lang.get("rawVideo"); - } - this.getRemoteVideoDuration(url,function(duration) { - _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}, atEnd : atEnd}}); - callback(); - return; - }); - } - ,refreshPlayer: function() { - this.player.refresh(); - } - ,getPlaylistLinks: function() { - var items = this.player.getItems(); - var _g = []; - var _g1 = 0; - while(_g1 < items.length) _g.push(items[_g1++].url); - return _g; - } - ,getRemoteVideoDuration: function(src,callback) { - var player = window.document.querySelector("#ytapiplayer"); - var video = window.document.createElement("video"); - video.src = src; - video.onerror = function(e) { - callback(0); - return; - }; - video.onloadedmetadata = function() { - if(player.contains(video)) { - player.removeChild(video); - } - callback(video.duration); - return; - }; - client_Utils.prepend(player,video); - } - ,onMessage: function(e) { - var data = JSON.parse(e.data); - var t = data.type; - var t1 = t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null); - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 171, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); - switch(data.type) { - case "AddVideo": - if(this.player.isListEmpty()) { - this.player.setVideo(data.addVideo.item); - } - this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); - break; - case "ClearChat": - window.document.querySelector("#messagebuffer").innerHTML = ""; - break; - case "ClearPlaylist": - this.player.clearItems(); - if(this.player.isListEmpty()) { - this.player.pause(); - } - break; - case "Connected": - this.onConnected(data); - this.onTimeGet.run(); - break; - case "GetTime": - var newTime = data.getTime.time; - var time = this.player.getTime(); - if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { - if(Math.abs(time - newTime) < 2) { - return; - } - this.player.setTime(time,false); - return; - } - if(!data.getTime.paused) { - this.player.play(); - } else { - this.player.pause(); - } - if(Math.abs(time - newTime) < 2) { - return; - } - this.player.setTime(newTime); - break; - case "Login": - this.onLogin(data.login.clients,data.login.clientName); - break; - case "LoginError": - var text = StringTools.replace(Lang.get("usernameError"),"$MAX","" + this.config.maxLoginLength); - this.serverMessage(4,text); - break; - case "Logout": - this.updateClients(data.logout.clients); - this.personal = new Client(null,null,data.logout.clientName,0); - this.showGuestLoginPanel(); - break; - case "Message": - this.addMessage(data.message.clientName,data.message.text); - break; - case "Pause": - if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { - return; - } - this.player.pause(); - this.player.setTime(data.pause.time); - break; - case "Play": - if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { - return; - } - this.player.setTime(data.play.time); - this.player.play(); - break; - case "RemoveVideo": - this.player.removeItem(data.removeVideo.url); - if(this.player.isListEmpty()) { - this.player.pause(); - } - break; - case "Rewind": - this.player.setTime(data.rewind.time); - break; - case "SetLeader": - ClientTools.setLeader(this.clients,data.setLeader.clientName); - this.updateUserList(); - this.setLeaderButton((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0); - if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { - this.player.setTime(this.player.getTime(),false); - } - break; - case "SetTime": - var newTime1 = data.setTime.time; - var time1 = this.player.getTime(); - if(Math.abs(time1 - newTime1) < 2) { - return; - } - this.player.setTime(newTime1); - break; - case "ShufflePlaylist": - break; - case "UpdateClients": - this.updateClients(data.updateClients.clients); - this.personal = ClientTools.getByName(this.clients,this.personal.name,this.personal); - break; - case "UpdatePlaylist": - this.player.setItems(data.updatePlaylist.videoList); - break; - case "VideoLoaded": - this.player.setTime(0); - this.player.play(); - break; - } - } - ,onConnected: function(data) { - var connected = data.connected; - this.setConfig(connected.config); - if(connected.isUnknownClient) { - this.updateClients(connected.clients); - this.personal = ClientTools.getByName(this.clients,connected.clientName,this.personal); - this.showGuestLoginPanel(); - } else { - this.onLogin(connected.clients,connected.clientName); - } - var guestName = window.document.querySelector("#guestname"); - if(guestName.value.length > 0) { - this.send({ type : "Login", login : { clientName : guestName.value}}); - } - var _g = 0; - var _g1 = connected.history; - while(_g < _g1.length) { - var message = _g1[_g]; - ++_g; - this.addMessage(message.name,message.text,message.time); - } - this.player.setItems(connected.videoList); - } - ,setConfig: function(config) { - this.config = config; - this.pageTitle = config.channelName; - window.document.querySelector("#guestname").maxLength = config.maxLoginLength; - window.document.querySelector("#chatline").maxLength = config.maxMessageLength; - this.filters.length = 0; - var _g = 0; - var _g1 = config.filters; - while(_g < _g1.length) { - var filter = _g1[_g]; - ++_g; - this.filters.push({ regex : new EReg(filter.regex,filter.flags), replace : filter.replace}); - } - var _g2 = 0; - var _g3 = config.emotes; - while(_g2 < _g3.length) { - var emote = _g3[_g2]; - ++_g2; - this.filters.push({ regex : new EReg(this.escapeRegExp(emote.name),"g"), replace : ""}); - } - var smilesWrap = window.document.querySelector("#smileswrap"); - smilesWrap.onclick = function(e) { - var el = e.target; - var form = window.document.querySelector("#chatline"); - form.value += " " + el.title; - form.focus(); - return; - }; - smilesWrap.innerHTML = ""; - var _g4 = 0; - var _g5 = config.emotes; - while(_g4 < _g5.length) { - var emote1 = _g5[_g4]; - ++_g4; - var img = window.document.createElement("img"); - img.className = "smile-preview"; - img.src = emote1.image; - img.title = emote1.name; - smilesWrap.appendChild(img); - } - } - ,onLogin: function(data,clientName) { - this.updateClients(data); - var newPersonal = ClientTools.getByName(this.clients,clientName); - if(newPersonal == null) { - return; - } - this.personal = newPersonal; - this.hideGuestLoginPanel(); - } - ,showGuestLoginPanel: function() { - window.document.querySelector("#guestlogin").style.display = "block"; - window.document.querySelector("#chatline").style.display = "none"; - } - ,hideGuestLoginPanel: function() { - window.document.querySelector("#guestlogin").style.display = "none"; - window.document.querySelector("#chatline").style.display = "block"; - } - ,updateClients: function(newClients) { - this.clients.length = 0; - var _g = 0; - while(_g < newClients.length) this.clients.push(Client.fromData(newClients[_g++])); - this.updateUserList(); - } - ,send: function(data) { - if(!this.isConnected) { - return; - } - this.ws.send(JSON.stringify(data)); - } - ,serverMessage: function(type,text) { - var msgBuf = window.document.querySelector("#messagebuffer"); - var div = window.document.createElement("div"); - var time = "[" + new Date().toTimeString().split(" ")[0] + "] "; - switch(type) { - case 1: - div.className = "server-msg-reconnect"; - div.innerHTML = Lang.get("msgConnected"); - break; - case 2: - div.className = "server-msg-disconnect"; - div.innerHTML = Lang.get("msgDisconnected"); - break; - case 3: - div.className = "server-whisper"; - div.innerHTML = time + text + " " + Lang.get("entered"); - break; - case 4: - div.className = "server-whisper"; - div.innerHTML = time + text; - break; - default: - } - msgBuf.appendChild(div); - msgBuf.scrollTop = msgBuf.scrollHeight; - } - ,updateUserList: function() { - window.document.querySelector("#usercount").innerHTML = this.clients.length + " " + Lang.get("online"); - window.document.title = this.getPageTitle(); - var list_b = ""; - var _g = 0; - var _g1 = this.clients; - while(_g < _g1.length) { - var client1 = _g1[_g]; - ++_g; - list_b += "
"; - if((client1.group & 2) != 0) { - list_b += ""; - } - list_b += Std.string("" + client1.name + "
"); - } - window.document.querySelector("#userlist").innerHTML = list_b; - } - ,getPageTitle: function() { - return "" + this.pageTitle + " (" + this.clients.length + ")"; - } - ,addMessage: function(name,text,time) { - var _gthis = this; - var msgBuf = window.document.querySelector("#messagebuffer"); - var userDiv = window.document.createElement("div"); - userDiv.className = "chat-msg-" + name; - var tstamp = window.document.createElement("span"); - tstamp.className = "timestamp"; - if(time == null) { - time = "[" + new Date().toTimeString().split(" ")[0] + "] "; - } - tstamp.innerHTML = time; - var nameDiv = window.document.createElement("strong"); - nameDiv.className = "username"; - nameDiv.innerHTML = name + ": "; - var textDiv = window.document.createElement("span"); - if(StringTools.startsWith(text,"/")) { - if(name == this.personal.name) { - this.handleCommands(HxOverrides.substr(text,1,null)); - } - } else { - var _g = 0; - var _g1 = this.filters; - while(_g < _g1.length) { - var filter = _g1[_g]; - ++_g; - text = text.replace(filter.regex.r,filter.replace); - } - } - textDiv.innerHTML = text; - var isInChatEnd = msgBuf.scrollHeight - msgBuf.scrollTop == msgBuf.clientHeight; - userDiv.appendChild(tstamp); - userDiv.appendChild(nameDiv); - userDiv.appendChild(textDiv); - msgBuf.appendChild(userDiv); - if(isInChatEnd) { - while(msgBuf.children.length > 200) msgBuf.removeChild(msgBuf.firstChild); - msgBuf.scrollTop = msgBuf.scrollHeight; - } - if(name == this.personal.name) { - msgBuf.scrollTop = msgBuf.scrollHeight; - } - if(window.document.hidden && this.onBlinkTab == null) { - this.onBlinkTab = new haxe_Timer(1000); - this.onBlinkTab.run = function() { - if(StringTools.startsWith(window.document.title,_gthis.pageTitle)) { - return window.document.title = "*Chat*"; - } else { - return window.document.title = _gthis.getPageTitle(); - } - }; - this.onBlinkTab.run(); - } - } - ,handleCommands: function(text) { - if(text == "clear") { - if((this.personal.group & 4) != 0) { - this.send({ type : "ClearChat"}); - } - } - if(this.matchNumbers.match(text)) { - this.send({ type : "Rewind", rewind : { time : Std.parseInt(text)}}); - } - } - ,setLeaderButton: function(flag) { - var leaderBtn = window.document.querySelector("#leader_btn"); - if((this.personal.group & 2) != 0) { - leaderBtn.classList.add("label-success"); - } else { - leaderBtn.classList.remove("label-success"); - } - } - ,escapeRegExp: function(regex) { - var _this_r = new RegExp("([.*+?^${}()|[\\]\\\\])","g".split("u").join("")); - return regex.replace(_this_r,"\\$1"); - } -}; -var client_MobileView = function() { }; -client_MobileView.__name__ = true; -client_MobileView.init = function() { - var mvbtn = window.document.querySelector("#mv_btn"); - mvbtn.onclick = function(e) { - if(client_Utils.toggleFullScreen(window.document.documentElement)) { - window.document.body.classList.add("mobile-view"); - mvbtn.classList.add("active"); - var vwrap = window.document.querySelector("#videowrap"); - if(vwrap.children[0] == window.document.querySelector("currenttitle")) { - vwrap.appendChild(vwrap.children[0]); - } - } else { - window.document.body.classList.remove("mobile-view"); - mvbtn.classList.remove("active"); - var vwrap1 = window.document.querySelector("videowrap"); - if(vwrap1.children[0] != window.document.querySelector("currenttitle")) { - vwrap1.insertBefore(vwrap1.children[1],vwrap1.children[0]); - } - } - return; - }; -}; -var client_Player = function(main) { - this.skipSetTime = false; - this.isLoaded = false; - this.player = window.document.querySelector("#ytapiplayer"); - this.videoItemsEl = window.document.querySelector("#queue"); - this.items = []; - this.main = main; -}; -client_Player.__name__ = true; -client_Player.prototype = { - setVideo: function(item) { - var _gthis = this; - this.isLoaded = false; - this.video = window.document.createElement("video"); - this.video.id = "videoplayer"; - this.video.src = item.url; - this.video.controls = true; - this.video.oncanplaythrough = function(e) { - if(!_gthis.isLoaded) { - _gthis.main.send({ type : "VideoLoaded"}); - } - return _gthis.isLoaded = true; - }; - this.video.onseeking = function(e1) { - if(_gthis.skipSetTime) { - _gthis.skipSetTime = false; - return; - } - if((_gthis.main.personal.group & 2) == 0) { - return; - } - _gthis.main.send({ type : "SetTime", setTime : { time : _gthis.video.currentTime}}); - return; - }; - this.video.onpause = function(e2) { - if((_gthis.main.personal.group & 2) == 0) { - return; - } - _gthis.main.send({ type : "Pause", pause : { time : _gthis.video.currentTime}}); - return; - }; - this.video.onplay = function(e3) { - if((_gthis.main.personal.group & 2) == 0) { - return; - } - _gthis.main.send({ type : "Play", play : { time : _gthis.video.currentTime}}); - return; - }; - this.player.innerHTML = ""; - this.player.appendChild(this.video); - window.document.querySelector("#currenttitle").innerHTML = item.title; - } - ,addVideoItem: function(item,atEnd) { - var _gthis = this; - this.items.push(item); - var itemEl = this.nodeFromString("
  • \n\t\t\t\t" + item.title + "\n\t\t\t\t" + this.duration(item.duration) + "\n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
    \n\t\t\t
  • "); - itemEl.querySelector("#btn-delete").onclick = function(e) { - _gthis.main.send({ type : "RemoveVideo", removeVideo : { url : itemEl.querySelector(".qe_title").getAttribute("href")}}); - return; - }; - if(atEnd) { - this.videoItemsEl.appendChild(itemEl); - } else { - client_Utils.insertAtIndex(this.videoItemsEl,itemEl,1); - } - this.updateCounters(); - } - ,removeVideo: function() { - if(this.video == null) { - return; - } - this.player.removeChild(this.video); - this.video = null; - window.document.querySelector("#currenttitle").innerHTML = Lang.get("nothingPlaying"); - } - ,removeItem: function(url) { - var _g = 0; - var _g1 = this.videoItemsEl.children; - while(_g < _g1.length) { - var child = _g1[_g]; - ++_g; - if(child.querySelector(".qe_title").getAttribute("href") == url) { - this.videoItemsEl.removeChild(child); - break; - } - } - HxOverrides.remove(this.items,Lambda.find(this.items,function(item) { - return item.url == url; - })); - if(this.video.src == url) { - if(this.items.length > 0) { - this.setVideo(this.items[0]); - } - } - this.updateCounters(); - } - ,updateCounters: function() { - var tmp = "" + this.items.length + " "; - var tmp1 = Lang.get("videos"); - window.document.querySelector("#plcount").innerHTML = tmp + tmp1; - window.document.querySelector("#pllength").innerHTML = this.totalDuration(); - } - ,getItems: function() { - return this.items; - } - ,setItems: function(list) { - this.clearItems(); - if(list.length == 0) { - return; - } - if(this.video == null || this.video.src != list[0].url) { - this.setVideo(list[0]); - } - var _g = 0; - while(_g < list.length) this.addVideoItem(list[_g++],true); - } - ,clearItems: function() { - this.items.length = 0; - this.videoItemsEl.innerHTML = ""; - this.updateCounters(); - } - ,refresh: function() { - if(this.items.length == 0) { - return; - } - this.removeVideo(); - this.setVideo(this.items[0]); - } - ,duration: function(time) { - var h = time / 60 / 60 | 0; - var m = (time / 60 | 0) - h * 60; - var s = time % 60 | 0; - var time1 = "" + m + ":"; - if(m < 10) { - time1 = "0" + time1; - } - if(h > 0) { - time1 = "" + h + ":" + time1; - } - if(s < 10) { - time1 += "0"; - } - time1 += s; - return time1; - } - ,totalDuration: function() { - var time = 0.0; - var _g = 0; - var _g1 = this.items; - while(_g < _g1.length) time += _g1[_g++].duration; - return this.duration(time); - } - ,nodeFromString: function(div) { - var wrapper = window.document.createElement("div"); - wrapper.innerHTML = div; - return wrapper.firstElementChild; - } - ,isListEmpty: function() { - return this.items.length == 0; - } - ,pause: function() { - if(this.video == null) { - return; - } - this.video.pause(); - } - ,play: function() { - if(this.video == null) { - return; - } - this.video.play(); - } - ,setTime: function(time,isLocal) { - if(isLocal == null) { - isLocal = true; - } - if(this.video == null) { - return; - } - this.skipSetTime = isLocal; - this.video.currentTime = time; - } - ,getTime: function() { - if(this.video == null) { - return 0; - } - return this.video.currentTime; - } -}; -var client_Utils = function() { }; -client_Utils.__name__ = true; -client_Utils.prepend = function(parent,child) { - if(parent.firstChild == null) { - parent.appendChild(child); - } else { - parent.insertBefore(child,parent.firstChild); - } -}; -client_Utils.insertAtIndex = function(parent,child,i) { - if(i >= parent.children.length) { - parent.appendChild(child); - } else { - parent.insertBefore(child,parent.children[i]); - } -}; -client_Utils.toggleFullScreen = function(el) { - var state = true; - var doc = window.document; - var el2 = el; - if(window.document.fullscreenElement == null && doc.mozFullScreenElement == null && doc.webkitFullscreenElement == null) { - if(el.requestFullscreen != null) { - el.requestFullscreen(); - } else if(el2.mozRequestFullScreen != null) { - el2.mozRequestFullScreen(); - } else if(el2.webkitRequestFullscreen != null) { - el2.webkitRequestFullscreen(HTMLElement.ALLOW_KEYBOARD_INPUT); - } else { - state = false; - } - } else { - if(doc.cancelFullScreen != null) { - doc.cancelFullScreen(); - } else if(doc.mozCancelFullScreen != null) { - doc.mozCancelFullScreen(); - } else if(doc.webkitCancelFullScreen != null) { - doc.webkitCancelFullScreen(); - } - state = false; - } - return state; -}; -client_Utils.copyToClipboard = function(text) { - var clipboardData = window.clipboardData; - if(clipboardData != null && clipboardData.setData != null) { - clipboardData.setData("Text",text); - return; - } else if(window.document.queryCommandSupported != null) { - var textarea = window.document.createElement("textarea"); - textarea.textContent = text; - textarea.style.position = "fixed"; - window.document.body.appendChild(textarea); - textarea.select(); - window.document.execCommand("copy"); - window.document.body.removeChild(textarea); - } -}; -var haxe_Log = function() { }; -haxe_Log.__name__ = true; -haxe_Log.formatOutput = function(v,infos) { - var str = Std.string(v); - if(infos == null) { - return str; - } - var pstr = infos.fileName + ":" + infos.lineNumber; - if(infos.customParams != null) { - var _g = 0; - var _g1 = infos.customParams; - while(_g < _g1.length) str += ", " + Std.string(_g1[_g++]); - } - return pstr + ": " + str; -}; -haxe_Log.trace = function(v,infos) { - var str = haxe_Log.formatOutput(v,infos); - if(typeof(console) != "undefined" && console.log != null) { - console.log(str); - } -}; -var haxe_Timer = function(time_ms) { - var me = this; - this.id = setInterval(function() { - me.run(); - },time_ms); -}; -haxe_Timer.__name__ = true; -haxe_Timer.delay = function(f,time_ms) { - var t = new haxe_Timer(time_ms); - t.run = function() { - t.stop(); - f(); - }; - return t; -}; -haxe_Timer.prototype = { - stop: function() { - if(this.id == null) { - return; - } - clearInterval(this.id); - this.id = null; - } - ,run: function() { - } -}; -var haxe_http_HttpBase = function(url) { - this.url = url; - this.headers = []; - this.params = []; - this.emptyOnData = $bind(this,this.onData); -}; -haxe_http_HttpBase.__name__ = true; -haxe_http_HttpBase.prototype = { - onData: function(data) { - } - ,onBytes: function(data) { - } - ,onError: function(msg) { - } - ,onStatus: function(status) { - } - ,hasOnData: function() { - return !Reflect.compareMethods($bind(this,this.onData),this.emptyOnData); - } - ,success: function(data) { - this.responseBytes = data; - this.responseAsString = null; - if(this.hasOnData()) { - this.onData(this.get_responseData()); - } - this.onBytes(this.responseBytes); - } - ,get_responseData: function() { - if(this.responseAsString == null && this.responseBytes != null) { - this.responseAsString = this.responseBytes.getString(0,this.responseBytes.length,haxe_io_Encoding.UTF8); - } - return this.responseAsString; - } -}; -var haxe_http_HttpJs = function(url) { - this.async = true; - this.withCredentials = false; - haxe_http_HttpBase.call(this,url); -}; -haxe_http_HttpJs.__name__ = true; -haxe_http_HttpJs.__super__ = haxe_http_HttpBase; -haxe_http_HttpJs.prototype = $extend(haxe_http_HttpBase.prototype,{ - request: function(post) { - var _gthis = this; - this.responseAsString = null; - this.responseBytes = null; - var r = this.req = js_Browser.createXMLHttpRequest(); - var onreadystatechange = function(_) { - if(r.readyState != 4) { - return; - } - var s; - try { - s = r.status; - } catch( e ) { - s = null; - } - if(s == 0 && typeof(window) != "undefined") { - var protocol = window.location.protocol.toLowerCase(); - if(new EReg("^(?:about|app|app-storage|.+-extension|file|res|widget):$","").match(protocol)) { - s = r.response != null ? 200 : 404; - } - } - if(s == undefined) { - s = null; - } - if(s != null) { - _gthis.onStatus(s); - } - if(s != null && s >= 200 && s < 400) { - _gthis.req = null; - var onreadystatechange1 = haxe_io_Bytes.ofData(r.response); - _gthis.success(onreadystatechange1); - } else if(s == null) { - _gthis.req = null; - _gthis.onError("Failed to connect or resolve host"); - } else if(s == null) { - _gthis.req = null; - _gthis.responseBytes = haxe_io_Bytes.ofData(r.response); - _gthis.onError("Http Error #" + r.status); - } else { - switch(s) { - case 12007: - _gthis.req = null; - _gthis.onError("Unknown host"); - break; - case 12029: - _gthis.req = null; - _gthis.onError("Failed to connect to host"); - break; - default: - _gthis.req = null; - _gthis.responseBytes = haxe_io_Bytes.ofData(r.response); - _gthis.onError("Http Error #" + r.status); - } - } - }; - if(this.async) { - r.onreadystatechange = onreadystatechange; - } - var _g = this.postBytes; - var _g1 = this.postData; - var uri = _g1 == null ? _g == null ? null : new Blob([_g.b.bufferValue]) : _g == null ? _g1 : null; - if(uri != null) { - post = true; - } else { - var _g2 = 0; - var _g3 = this.params; - while(_g2 < _g3.length) { - var p = _g3[_g2]; - ++_g2; - if(uri == null) { - uri = ""; - } else { - uri = Std.string(uri) + "&"; - } - var s1 = p.name; - var value = Std.string(uri) + encodeURIComponent(s1) + "="; - var s2 = p.value; - uri = value + encodeURIComponent(s2); - } - } - try { - if(post) { - r.open("POST",this.url,this.async); - } else if(uri != null) { - r.open("GET",this.url + (this.url.split("?").length <= 1 ? "?" : "&") + Std.string(uri),this.async); - uri = null; - } else { - r.open("GET",this.url,this.async); - } - r.responseType = "arraybuffer"; - } catch( e1 ) { - var e2 = ((e1) instanceof js__$Boot_HaxeError) ? e1.val : e1; - this.req = null; - this.onError(e2.toString()); - return; - } - r.withCredentials = this.withCredentials; - if(!Lambda.exists(this.headers,function(h) { - return h.name == "Content-Type"; - }) && post && this.postData == null) { - r.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); - } - var _g21 = 0; - var _g31 = this.headers; - while(_g21 < _g31.length) { - var h1 = _g31[_g21]; - ++_g21; - r.setRequestHeader(h1.name,h1.value); - } - r.send(uri); - if(!this.async) { - onreadystatechange(null); - } - } -}); -var haxe_io_Bytes = function(data) { - this.length = data.byteLength; - this.b = new Uint8Array(data); - this.b.bufferValue = data; - data.hxBytes = this; - data.bytes = this.b; -}; -haxe_io_Bytes.__name__ = true; -haxe_io_Bytes.ofData = function(b) { - var hb = b.hxBytes; - if(hb != null) { - return hb; - } - return new haxe_io_Bytes(b); -}; -haxe_io_Bytes.prototype = { - getString: function(pos,len,encoding) { - if(pos < 0 || len < 0 || pos + len > this.length) { - throw new js__$Boot_HaxeError(haxe_io_Error.OutsideBounds); - } - if(encoding == null) { - encoding = haxe_io_Encoding.UTF8; - } - var s = ""; - var b = this.b; - var i = pos; - var max = pos + len; - switch(encoding._hx_index) { - case 0: - while(i < max) { - var c = b[i++]; - if(c < 128) { - if(c == 0) { - break; - } - s += String.fromCodePoint(c); - } else if(c < 224) { - var code = (c & 63) << 6 | b[i++] & 127; - s += String.fromCodePoint(code); - } else if(c < 240) { - var code1 = (c & 31) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; - s += String.fromCodePoint(code1); - } else { - var u = (c & 15) << 18 | (b[i++] & 127) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; - s += String.fromCodePoint(u); - } - } - break; - case 1: - while(i < max) { - var c1 = b[i++] | b[i++] << 8; - s += String.fromCodePoint(c1); - } - break; - } - return s; - } -}; -var haxe_io_Encoding = $hxEnums["haxe.io.Encoding"] = { __ename__ : true, __constructs__ : ["UTF8","RawNative"] - ,UTF8: {_hx_index:0,__enum__:"haxe.io.Encoding",toString:$estr} - ,RawNative: {_hx_index:1,__enum__:"haxe.io.Encoding",toString:$estr} -}; -var haxe_io_Error = $hxEnums["haxe.io.Error"] = { __ename__ : true, __constructs__ : ["Blocked","Overflow","OutsideBounds","Custom"] - ,Blocked: {_hx_index:0,__enum__:"haxe.io.Error",toString:$estr} - ,Overflow: {_hx_index:1,__enum__:"haxe.io.Error",toString:$estr} - ,OutsideBounds: {_hx_index:2,__enum__:"haxe.io.Error",toString:$estr} - ,Custom: ($_=function(e) { return {_hx_index:3,e:e,__enum__:"haxe.io.Error",toString:$estr}; },$_.__params__ = ["e"],$_) -}; -var haxe_io_Path = function(path) { - switch(path) { - case ".":case "..": - this.dir = path; - this.file = ""; - return; - } - var c1 = path.lastIndexOf("/"); - var c2 = path.lastIndexOf("\\"); - if(c1 < c2) { - this.dir = HxOverrides.substr(path,0,c2); - path = HxOverrides.substr(path,c2 + 1,null); - this.backslash = true; - } else if(c2 < c1) { - this.dir = HxOverrides.substr(path,0,c1); - path = HxOverrides.substr(path,c1 + 1,null); - } else { - this.dir = null; - } - var cp = path.lastIndexOf("."); - if(cp != -1) { - this.ext = HxOverrides.substr(path,cp + 1,null); - this.file = HxOverrides.substr(path,0,cp); - } else { - this.ext = null; - this.file = path; - } -}; -haxe_io_Path.__name__ = true; -haxe_io_Path.withoutExtension = function(path) { - var s = new haxe_io_Path(path); - s.ext = null; - return s.toString(); -}; -haxe_io_Path.prototype = { - toString: function() { - return (this.dir == null ? "" : this.dir + (this.backslash ? "\\" : "/")) + this.file + (this.ext == null ? "" : "." + this.ext); - } -}; -var js__$Boot_HaxeError = function(val) { - Error.call(this); - this.val = val; - if(Error.captureStackTrace) { - Error.captureStackTrace(this,js__$Boot_HaxeError); - } -}; -js__$Boot_HaxeError.__name__ = true; -js__$Boot_HaxeError.__super__ = Error; -js__$Boot_HaxeError.prototype = $extend(Error.prototype,{ -}); -var js_Boot = function() { }; -js_Boot.__name__ = true; -js_Boot.__string_rec = function(o,s) { - if(o == null) { - return "null"; - } - if(s.length >= 5) { - return "<...>"; - } - var t = typeof(o); - if(t == "function" && (o.__name__ || o.__ename__)) { - t = "object"; - } - switch(t) { - case "function": - return ""; - case "object": - if(o.__enum__) { - var e = $hxEnums[o.__enum__]; - var n = e.__constructs__[o._hx_index]; - var con = e[n]; - if(con.__params__) { - s = s + "\t"; - return n + "(" + ((function($this) { - var $r; - var _g = []; - { - var _g1 = 0; - var _g2 = con.__params__; - while(true) { - if(!(_g1 < _g2.length)) { - break; - } - var p = _g2[_g1]; - _g1 = _g1 + 1; - _g.push(js_Boot.__string_rec(o[p],s)); - } - } - $r = _g; - return $r; - }(this))).join(",") + ")"; - } else { - return n; - } - } - if(((o) instanceof Array)) { - var str = "["; - s += "\t"; - var _g3 = 0; - var _g11 = o.length; - while(_g3 < _g11) { - var i = _g3++; - str += (i > 0 ? "," : "") + js_Boot.__string_rec(o[i],s); - } - str += "]"; - return str; - } - var tostr; - try { - tostr = o.toString; - } catch( e1 ) { - var e2 = ((e1) instanceof js__$Boot_HaxeError) ? e1.val : e1; - return "???"; - } - if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") { - var s2 = o.toString(); - if(s2 != "[object Object]") { - return s2; - } - } - var str1 = "{\n"; - s += "\t"; - var hasp = o.hasOwnProperty != null; - var k = null; - for( k in o ) { - if(hasp && !o.hasOwnProperty(k)) { - continue; - } - if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") { - continue; - } - if(str1.length != 2) { - str1 += ", \n"; - } - str1 += s + k + " : " + js_Boot.__string_rec(o[k],s); - } - s = s.substring(1); - str1 += "\n" + s + "}"; - return str1; - case "string": - return o; - default: - return String(o); - } -}; -var js_Browser = function() { }; -js_Browser.__name__ = true; -js_Browser.createXMLHttpRequest = function() { - if(typeof XMLHttpRequest != "undefined") { - return new XMLHttpRequest(); - } - if(typeof ActiveXObject != "undefined") { - return new ActiveXObject("Microsoft.XMLHTTP"); - } - throw new js__$Boot_HaxeError("Unable to create XMLHttpRequest object."); -}; -function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else return o.iterator(); } -function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; } -$global.$haxeUID |= 0; -var __map_reserved = {}; -if( String.fromCodePoint == null ) String.fromCodePoint = function(c) { return c < 0x10000 ? String.fromCharCode(c) : String.fromCharCode((c>>10)+0xD7C0)+String.fromCharCode((c&0x3FF)+0xDC00); } -String.__name__ = true; -Array.__name__ = true; -Object.defineProperty(js__$Boot_HaxeError.prototype,"message",{ get : function() { - return String(this.val); -}}); -js_Boot.__toStr = ({ }).toString; -Lang.ids = ["en","ru"]; -Lang.langs = new haxe_ds_StringMap(); -Lang.lang = HxOverrides.substr(window.navigator.language,0,2).toLowerCase(); -client_Buttons.personalHistory = []; -client_Buttons.personalHistoryId = -1; -client_Main.main(); -})(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/build/server.js b/build/server.js index 68ad69d..ce5bb7b 100644 --- a/build/server.js +++ b/build/server.js @@ -12,8 +12,9 @@ var ClientGroup = $hxEnums["ClientGroup"] = { __ename__ : true, __constructs__ : ,Leader: {_hx_index:1,__enum__:"ClientGroup",toString:$estr} ,Admin: {_hx_index:2,__enum__:"ClientGroup",toString:$estr} }; -var Client = function(ws,id,name,group) { +var Client = function(ws,req,id,name,group) { this.ws = ws; + this.req = req; this.id = id; this.name = name; var i = group; @@ -308,6 +309,9 @@ StringTools.startsWith = function(s,start) { return false; } }; +StringTools.replace = function(s,sub,by) { + return s.split(sub).join(by); +}; var haxe_Log = function() { }; haxe_Log.__name__ = true; haxe_Log.formatOutput = function(v,infos) { @@ -514,36 +518,49 @@ var js_node_Path = require("path"); var js_npm_ws_Server = require("ws").Server; var server_HttpServer = function() { }; server_HttpServer.__name__ = true; -server_HttpServer.init = function(directory) { - server_HttpServer.dir = directory; +server_HttpServer.init = function(dir,customDir) { + server_HttpServer.dir = dir; + if(customDir == null) { + return; + } + server_HttpServer.customDir = customDir; + server_HttpServer.hasCustomRes = sys_FileSystem.exists(customDir); }; server_HttpServer.serveFiles = function(req,res) { - var filePath = server_HttpServer.dir + req.url; - if(req.url == "/") { - filePath = "" + server_HttpServer.dir + "/index.html"; + var url = req.url; + if(url == "/") { + url = "/index.html"; } + var filePath = server_HttpServer.dir + url; var extension = haxe_io_Path.extension(filePath).toLowerCase(); var contentType = server_HttpServer.getMimeType(extension); + var tmp; + if(req.connection.remoteAddress != req.connection.localAddress) { + var _this = server_HttpServer.allowedLocalFiles; + tmp = __map_reserved[url] != null ? _this.getReserved(url) : _this.h[url]; + } else { + tmp = true; + } + if(tmp) { + if(server_HttpServer.serveLocalFile(res,url,extension,contentType)) { + return; + } + } if(!server_HttpServer.isChildOf(server_HttpServer.dir,filePath)) { res.statusCode = 500; - var tmp = "Error getting the file: No access to " + js_node_Path.relative(server_HttpServer.dir,filePath) + "."; - res.end(tmp); + var tmp1 = "Error getting the file: No access to " + js_node_Path.relative(server_HttpServer.dir,filePath) + "."; + res.end(tmp1); return; } - if(filePath == "" + server_HttpServer.dir + "/client.js") { - filePath = "" + __dirname + "/client.js"; + if(server_HttpServer.hasCustomRes) { + var path = server_HttpServer.customDir + url; + if(js_node_Fs.existsSync(path)) { + filePath = path; + } } js_node_Fs.readFile(filePath,function(err,data) { if(err != null) { - if(err.code == "ENOENT") { - res.statusCode = 404; - var tmp1 = "File " + js_node_Path.relative(server_HttpServer.dir,filePath) + " not found."; - res.end(tmp1); - } else { - res.statusCode = 500; - var tmp2 = "Error getting the file: " + Std.string(err) + "."; - res.end(tmp2); - } + server_HttpServer.readFileError(err,res,filePath); return; } res.setHeader("Content-Type",contentType); @@ -551,24 +568,58 @@ server_HttpServer.serveFiles = function(req,res) { data = server_HttpServer.localizeHtml(data.toString(),req.headers["accept-language"]); } res.end(data); + return; }); }; +server_HttpServer.readFileError = function(err,res,filePath) { + if(err.code == "ENOENT") { + res.statusCode = 404; + res.end("File " + js_node_Path.relative(server_HttpServer.dir,filePath) + " not found."); + } else { + res.statusCode = 500; + res.end("Error getting the file: " + Std.string(err) + "."); + } +}; +server_HttpServer.serveLocalFile = function(res,filePath,ext,contentType) { + if(ext != "mp4" && ext != "mp3" && ext != "wav") { + return false; + } + if(!js_node_Fs.existsSync(filePath)) { + return false; + } + var _this = server_HttpServer.allowedLocalFiles; + if(__map_reserved[filePath] != null) { + _this.setReserved(filePath,true); + } else { + _this.h[filePath] = true; + } + js_node_Fs.readFile(filePath,function(err,data) { + if(err != null) { + server_HttpServer.readFileError(err,res,filePath); + return; + } + res.setHeader("Content-Type",contentType); + res.end(data); + return; + }); + return true; +}; server_HttpServer.localizeHtml = function(data,lang) { if(lang != null && server_HttpServer.matchLang.match(lang)) { lang = server_HttpServer.matchLang.matched(0); } else { lang = "en"; } - data = new EReg("\\${([A-z_]+)}","g").map(data,function(regExp) { + data = server_HttpServer.matchVarString.map(data,function(regExp) { var key = regExp.matched(1); return Lang.get(lang,key); }); return data; }; server_HttpServer.isChildOf = function(parent,child) { - var relative = js_node_Path.relative(parent,child); - if(relative.length > 0 && !StringTools.startsWith(relative,"..")) { - return !js_node_Path.isAbsolute(relative); + var rel = js_node_Path.relative(parent,child); + if(rel.length > 0 && !StringTools.startsWith(rel,"..")) { + return !js_node_Path.isAbsolute(rel); } else { return false; } @@ -577,7 +628,7 @@ server_HttpServer.getMimeType = function(ext) { var _this = server_HttpServer.mimeTypes; var contentType = __map_reserved[ext] != null ? _this.getReserved(ext) : _this.h[ext]; if(contentType == null) { - contentType = "application/octet-stream"; + return "application/octet-stream"; } return contentType; }; @@ -595,6 +646,7 @@ var server_Main = function(port,wsPort) { this.freeIds = []; this.clients = []; this.rootDir = "" + __dirname + "/.."; + var _gthis = this; this.config = this.getUserConfig(); this.wss = new js_npm_ws_Server({ port : wsPort}); this.wss.on("connection",$bind(this,this.onConnect)); @@ -603,21 +655,27 @@ var server_Main = function(port,wsPort) { }; process.on("exit",exit); process.on("SIGINT",exit); + process.on("SIGUSR1",exit); + process.on("SIGUSR2",exit); process.on("uncaughtException",function(log) { - haxe_Log.trace(log,{ fileName : "src/server/Main.hx", lineNumber : 41, className : "server.Main", methodName : "new"}); + haxe_Log.trace(log,{ fileName : "src/server/Main.hx", lineNumber : 49, className : "server.Main", methodName : "new"}); return; }); process.on("unhandledRejection",function(reason,promise) { - haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 44, className : "server.Main", methodName : "new", customParams : [reason]}); + haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 52, className : "server.Main", methodName : "new", customParams : [reason]}); return; }); + this.localIp = server_Utils.getLocalIp(); + this.globalIp = this.localIp; + this.port = port; server_Utils.getGlobalIp(function(ip) { - haxe_Log.trace("Local: http://" + server_Utils.getLocalIp() + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 49, className : "server.Main", methodName : "new"}); - haxe_Log.trace("Global: http://" + ip + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 50, className : "server.Main", methodName : "new"}); + _gthis.globalIp = ip; + haxe_Log.trace("Local: http://" + _gthis.localIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 60, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 61, className : "server.Main", methodName : "new"}); return; }); var dir = "" + this.rootDir + "/res"; - server_HttpServer.init(dir); + server_HttpServer.init(dir,"" + this.rootDir + "/user/res"); Lang.init("" + dir + "/langs"); js_node_Http.createServer(function(req,res) { server_HttpServer.serveFiles(req,res); @@ -631,7 +689,7 @@ server_Main.main = function() { server_Main.prototype = { getUserConfig: function() { var config = JSON.parse(js_node_Fs.readFileSync("" + this.rootDir + "/default-config.json",{ encoding : "utf8"})); - var customPath = "" + this.rootDir + "/config.json"; + var customPath = "" + this.rootDir + "/user/config.json"; if(!sys_FileSystem.exists(customPath)) { return config; } @@ -642,7 +700,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 : 68, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 79, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -653,8 +711,8 @@ 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 : 78, className : "server.Main", methodName : "onConnect"}); - var client = new Client(ws,id,name,0); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 89, className : "server.Main", methodName : "onConnect"}); + var client = new Client(ws,req,id,name,0); if(req.connection.localAddress == ip) { client.group |= 4; } @@ -671,7 +729,7 @@ server_Main.prototype = { var _g1 = 0; var _g2 = this.clients; while(_g1 < _g2.length) _g.push(_g2[_g1++].getData()); - this.send(client,{ type : "Connected", connected : { config : tmp, history : tmp1, isUnknownClient : true, clientName : client1, clients : _g, videoList : this.videoList}}); + this.send(client,{ type : "Connected", connected : { config : tmp, history : tmp1, isUnknownClient : true, clientName : client1, clients : _g, videoList : this.videoList, globalIp : this.globalIp}}); this.sendClientList(); ws.on("message",function(data) { var tmp2 = JSON.parse(data); @@ -679,8 +737,8 @@ server_Main.prototype = { return; }); ws.on("close",function(err) { - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 105, className : "server.Main", methodName : "onConnect"}); - _gthis.sortedPush(_gthis.freeIds,client.id); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 117, className : "server.Main", methodName : "onConnect"}); + server_Utils.sortedPush(_gthis.freeIds,client.id); HxOverrides.remove(_gthis.clients,client); _gthis.sendClientList(); if((client.group & 2) != 0) { @@ -697,25 +755,18 @@ server_Main.prototype = { return; }); } - ,sortedPush: function(ids,id) { - var _g = 0; - var _g1 = ids.length; - while(_g < _g1) { - var i = _g++; - if(id < ids[i]) { - ids.splice(i,0,id); - return; - } - } - ids.push(id); - } ,onMessage: function(client,data) { switch(data.type) { case "AddVideo": + var item = data.addVideo.item; + var localOrigin = "" + this.localIp + ":" + this.port; + if(item.url.indexOf(localOrigin) != -1) { + item.url = StringTools.replace(item.url,localOrigin,"" + this.globalIp + ":" + this.port); + } if(data.addVideo.atEnd) { - this.videoList.push(data.addVideo.item); + this.videoList.push(item); } else { - this.videoList.splice(1,0,data.addVideo.item); + this.videoList.splice(1,0,item); } this.broadcast(data); if(this.videoList.length == 1) { @@ -808,8 +859,8 @@ server_Main.prototype = { if(this.videoList[0].url == url) { this.videoTimer.stop(); } - HxOverrides.remove(this.videoList,Lambda.find(this.videoList,function(item) { - return item.url == url; + HxOverrides.remove(this.videoList,Lambda.find(this.videoList,function(item1) { + return item1.url == url; })); this.broadcast(data); if(this.videoList.length > 0) { @@ -954,6 +1005,18 @@ server_Utils.getLocalIp = function() { } return "127.0.0.1"; }; +server_Utils.sortedPush = function(ids,id) { + var _g = 0; + var _g1 = ids.length; + while(_g < _g1) { + var i = _g++; + if(id < ids[i]) { + ids.splice(i,0,id); + return; + } + } + ids.push(id); +}; server_Utils.shuffle = function(arr) { var _g = 0; var _g1 = arr.length; @@ -1127,6 +1190,9 @@ server_HttpServer.mimeTypes = (function($this) { $r = _g; return $r; }(this)); +server_HttpServer.hasCustomRes = false; +server_HttpServer.allowedLocalFiles = new haxe_ds_StringMap(); server_HttpServer.matchLang = new EReg("^[A-z]+",""); +server_HttpServer.matchVarString = new EReg("\\${([A-z_]+)}","g"); server_Main.main(); })(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/res/client.js b/res/client.js new file mode 100644 index 0000000..d9cc67b --- /dev/null +++ b/res/client.js @@ -0,0 +1,1677 @@ +// Generated by Haxe 4.0.5 +(function ($global) { "use strict"; +var $estr = function() { return js_Boot.__string_rec(this,''); },$hxEnums = $hxEnums || {},$_; +function $extend(from, fields) { + var proto = Object.create(from); + for (var name in fields) proto[name] = fields[name]; + if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; + return proto; +} +var ClientGroup = $hxEnums["ClientGroup"] = { __ename__ : true, __constructs__ : ["User","Leader","Admin"] + ,User: {_hx_index:0,__enum__:"ClientGroup",toString:$estr} + ,Leader: {_hx_index:1,__enum__:"ClientGroup",toString:$estr} + ,Admin: {_hx_index:2,__enum__:"ClientGroup",toString:$estr} +}; +var Client = function(name,group) { + this.name = name; + var i = group; + if(group == null) { + i = 0; + } + this.group = i; +}; +Client.__name__ = true; +Client.fromData = function(data) { + return new Client(data.name,data.group); +}; +Client.prototype = { + setGroupFlag: function(type,flag) { + if(flag) { + this.group |= 1 << type._hx_index; + } else { + this.group &= -1 - (1 << type._hx_index); + } + return flag; + } +}; +var ClientTools = function() { }; +ClientTools.__name__ = true; +ClientTools.setLeader = function(clients,name) { + var _g = 0; + while(_g < clients.length) { + var client = clients[_g]; + ++_g; + if(client.name == name) { + client.setGroupFlag(ClientGroup.Leader,true); + } else if((client.group & 2) != 0) { + client.setGroupFlag(ClientGroup.Leader,false); + } + } +}; +ClientTools.getByName = function(clients,name,def) { + var _g = 0; + while(_g < clients.length) { + var client = clients[_g]; + ++_g; + if(client.name == name) { + return client; + } + } + return def; +}; +var EReg = function(r,opt) { + this.r = new RegExp(r,opt.split("u").join("")); +}; +EReg.__name__ = true; +EReg.prototype = { + match: function(s) { + if(this.r.global) { + this.r.lastIndex = 0; + } + this.r.m = this.r.exec(s); + this.r.s = s; + return this.r.m != null; + } + ,matched: function(n) { + if(this.r.m != null && n >= 0 && n < this.r.m.length) { + return this.r.m[n]; + } else { + throw new js__$Boot_HaxeError("EReg::matched"); + } + } +}; +var HxOverrides = function() { }; +HxOverrides.__name__ = true; +HxOverrides.substr = function(s,pos,len) { + if(len == null) { + len = s.length; + } else if(len < 0) { + if(pos == 0) { + len = s.length + len; + } else { + return ""; + } + } + return s.substr(pos,len); +}; +HxOverrides.remove = function(a,obj) { + var i = a.indexOf(obj); + if(i == -1) { + return false; + } + a.splice(i,1); + return true; +}; +HxOverrides.iter = function(a) { + return { cur : 0, arr : a, hasNext : function() { + return this.cur < this.arr.length; + }, next : function() { + return this.arr[this.cur++]; + }}; +}; +var Lambda = function() { }; +Lambda.__name__ = true; +Lambda.exists = function(it,f) { + var x = $getIterator(it); + while(x.hasNext()) if(f(x.next())) { + return true; + } + return false; +}; +Lambda.find = function(it,f) { + var v = $getIterator(it); + while(v.hasNext()) { + var v1 = v.next(); + if(f(v1)) { + return v1; + } + } + return null; +}; +var haxe_ds_StringMap = function() { + this.h = { }; +}; +haxe_ds_StringMap.__name__ = true; +haxe_ds_StringMap.prototype = { + setReserved: function(key,value) { + if(this.rh == null) { + this.rh = { }; + } + this.rh["$" + key] = value; + } + ,getReserved: function(key) { + if(this.rh == null) { + return null; + } else { + return this.rh["$" + key]; + } + } +}; +var Lang = function() { }; +Lang.__name__ = true; +Lang.request = function(path,callback) { + var http = new haxe_http_HttpJs(path); + http.onData = callback; + http.request(); +}; +Lang.init = function(folderPath,callback) { + var _this = Lang.langs; + _this.h = { }; + _this.rh = null; + var count = 0; + var _g = 0; + var _g1 = Lang.ids; + while(_g < _g1.length) { + var name = [_g1[_g]]; + ++_g; + Lang.request("" + folderPath + "/" + name[0] + ".json",(function(name1) { + return function(data) { + var data1 = JSON.parse(data); + var lang = new haxe_ds_StringMap(); + var _g2 = 0; + var _g11 = Reflect.fields(data1); + while(_g2 < _g11.length) { + var key = _g11[_g2]; + ++_g2; + var v = Reflect.field(data1,key); + if(__map_reserved[key] != null) { + lang.setReserved(key,v); + } else { + lang.h[key] = v; + } + } + var id = haxe_io_Path.withoutExtension(name1[0]); + var _this1 = Lang.langs; + if(__map_reserved[id] != null) { + _this1.setReserved(id,lang); + } else { + _this1.h[id] = lang; + } + count += 1; + if(count == Lang.ids.length && callback != null) { + callback(); + } + return; + }; + })(name)); + } +}; +Lang.get = function(key) { + var key1 = Lang.lang; + var _this = Lang.langs; + if((__map_reserved[key1] != null ? _this.getReserved(key1) : _this.h[key1]) == null) { + Lang.lang = "en"; + } + var key2 = Lang.lang; + var _this1 = Lang.langs; + var _this2 = __map_reserved[key2] != null ? _this1.getReserved(key2) : _this1.h[key2]; + var text = __map_reserved[key] != null ? _this2.getReserved(key) : _this2.h[key]; + if(text == null) { + return key; + } else { + return text; + } +}; +Math.__name__ = true; +var Reflect = function() { }; +Reflect.__name__ = true; +Reflect.field = function(o,field) { + try { + return o[field]; + } catch( e ) { + return null; + } +}; +Reflect.fields = function(o) { + var a = []; + if(o != null) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + for( var f in o ) { + if(f != "__id__" && f != "hx__closures__" && hasOwnProperty.call(o,f)) { + a.push(f); + } + } + } + return a; +}; +Reflect.isFunction = function(f) { + if(typeof(f) == "function") { + return !(f.__name__ || f.__ename__); + } else { + return false; + } +}; +Reflect.compareMethods = function(f1,f2) { + if(f1 == f2) { + return true; + } + if(!Reflect.isFunction(f1) || !Reflect.isFunction(f2)) { + return false; + } + if(f1.scope == f2.scope && f1.method == f2.method) { + return f1.method != null; + } else { + return false; + } +}; +var Std = function() { }; +Std.__name__ = true; +Std.string = function(s) { + return js_Boot.__string_rec(s,""); +}; +Std.parseInt = function(x) { + if(x != null) { + var _g = 0; + var _g1 = x.length; + while(_g < _g1) { + var i = _g++; + var c = x.charCodeAt(i); + if(c <= 8 || c >= 14 && c != 32 && c != 45) { + var v = parseInt(x, (x[(i + 1)]=="x" || x[(i + 1)]=="X") ? 16 : 10); + if(isNaN(v)) { + return null; + } else { + return v; + } + } + } + } + return null; +}; +var StringTools = function() { }; +StringTools.__name__ = true; +StringTools.startsWith = function(s,start) { + if(s.length >= start.length) { + return s.lastIndexOf(start,0) == 0; + } else { + return false; + } +}; +StringTools.replace = function(s,sub,by) { + return s.split(sub).join(by); +}; +var client_Buttons = function() { }; +client_Buttons.__name__ = true; +client_Buttons.init = function(main) { + client_Buttons.initChatInput(main); + var smilesBtn = window.document.querySelector("#smilesbtn"); + smilesBtn.onclick = function(e) { + smilesBtn.classList.toggle("active"); + var smilesWrap = window.document.querySelector("#smileswrap"); + if(smilesBtn.classList.contains("active")) { + return smilesWrap.style.display = "block"; + } else { + return smilesWrap.style.display = "none"; + } + }; + window.document.querySelector("#clearchatbtn").style.display = "inline-block"; + window.document.querySelector("#clearchatbtn").onclick = function(e1) { + if((main.personal.group & 4) != 0) { + main.send({ type : "ClearChat"}); + } + return; + }; + var userList = window.document.querySelector("#userlist"); + userList.onclick = function(e2) { + if((main.personal.group & 4) == 0) { + return; + } + var el = e2.target; + if(userList == el) { + return; + } + if(!el.classList.contains("userlist_item")) { + el = el.parentElement; + } + var name = ""; + if(el.children.length == 1) { + name = el.lastElementChild.innerText; + } + main.send({ type : "SetLeader", setLeader : { clientName : name}}); + return; + }; + client_Buttons.split = new Split(["#chatwrap","#videowrap"],{ sizes : [40,60], onDragEnd : function() { + return window.dispatchEvent(new Event("resize")); + }, minSize : 185, snapOffset : 0}); + var userlistToggle = window.document.querySelector("#userlisttoggle"); + userlistToggle.onclick = function(e3) { + var style = window.document.querySelector("#userlist").style; + if(style.display == "none") { + userlistToggle.classList.add("glyphicon-chevron-down"); + userlistToggle.classList.remove("glyphicon-chevron-right"); + return style.display = "block"; + } else { + userlistToggle.classList.add("glyphicon-chevron-right"); + userlistToggle.classList.remove("glyphicon-chevron-down"); + return style.display = "none"; + } + }; + window.document.querySelector("#usercount").onclick = userlistToggle.onclick; + var extendPlayer = window.document.querySelector("#extendplayer"); + extendPlayer.onclick = function(e4) { + if(extendPlayer.classList.contains("active")) { + client_Buttons.split.setSizes([40,60]); + window.document.querySelector("#userlist").style.width = "90px"; + } else { + client_Buttons.split.setSizes([20,80]); + window.document.querySelector("#userlist").style.width = "80px"; + } + extendPlayer.classList.toggle("active"); + return window.dispatchEvent(new Event("resize")); + }; + window.document.querySelector("#mediarefresh").onclick = function(e5) { + main.refreshPlayer(); + return; + }; + window.document.querySelector("#fullscreenbtn").onclick = function(e6) { + return client_Utils.toggleFullScreen(window.document.querySelector("#ytapiplayer")); + }; + var getPlaylist = window.document.querySelector("#getplaylist"); + getPlaylist.onclick = function(e7) { + client_Utils.copyToClipboard(main.getPlaylistLinks().join(",")); + var icon = getPlaylist.firstElementChild; + icon.classList.remove("glyphicon-link"); + icon.classList.add("glyphicon-ok"); + return haxe_Timer.delay(function() { + icon.classList.add("glyphicon-link"); + icon.classList.remove("glyphicon-ok"); + return; + },2000); + }; + window.document.querySelector("#clearplaylist").onclick = function(e8) { + if(!window.confirm(Lang.get("clearPlaylistConfirm"))) { + return; + } + main.send({ type : "ClearPlaylist"}); + return; + }; + window.document.querySelector("#shuffleplaylist").onclick = function(e9) { + if(!window.confirm(Lang.get("shufflePlaylistConfirm"))) { + return; + } + main.send({ type : "ShufflePlaylist"}); + return; + }; + window.document.querySelector("#showmediaurl").onclick = function(e10) { + window.document.querySelector("#showmediaurl").classList.toggle("collapsed"); + window.document.querySelector("#showmediaurl").classList.toggle("active"); + return window.document.querySelector("#addfromurl").classList.toggle("collapse"); + }; + window.onresize = client_Buttons.onVideoResize; + window.dispatchEvent(new Event("resize")); +}; +client_Buttons.onVideoResize = function() { + var height = window.document.querySelector("#ytapiplayer").offsetHeight - window.document.querySelector("#chatline").offsetHeight; + window.document.querySelector("#messagebuffer").style.height = "" + height + "px"; + window.document.querySelector("#userlist").style.height = "" + height + "px"; +}; +client_Buttons.initChatInput = function(main) { + var guestName = window.document.querySelector("#guestname"); + guestName.onkeydown = function(e) { + if(guestName.value.length == 0) { + return; + } + if(e.keyCode == 13) { + main.send({ type : "Login", login : { clientName : guestName.value}}); + } + return; + }; + var chatLine = window.document.querySelector("#chatline"); + chatLine.onkeydown = function(e1) { + switch(e1.keyCode) { + case 13: + if(chatLine.value.length == 0) { + return; + } + main.send({ type : "Message", message : { clientName : "", text : chatLine.value}}); + client_Buttons.personalHistory.push(chatLine.value); + if(client_Buttons.personalHistory.length > 50) { + client_Buttons.personalHistory.shift(); + } + client_Buttons.personalHistoryId = -1; + chatLine.value = ""; + break; + case 38: + client_Buttons.personalHistoryId--; + if(client_Buttons.personalHistoryId == -2) { + client_Buttons.personalHistoryId = client_Buttons.personalHistory.length - 1; + if(client_Buttons.personalHistoryId == -1) { + return; + } + } else if(client_Buttons.personalHistoryId == -1) { + client_Buttons.personalHistoryId++; + } + chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; + break; + case 40: + if(client_Buttons.personalHistoryId == -1) { + return; + } + client_Buttons.personalHistoryId++; + if(client_Buttons.personalHistoryId > client_Buttons.personalHistory.length - 1) { + client_Buttons.personalHistoryId = -1; + chatLine.value = ""; + return; + } + chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; + break; + } + }; +}; +var client_Main = function(host,port) { + if(port == null) { + port = 4201; + } + this.matchNumbers = new EReg("^-?[0-9]+$",""); + this.onTimeGet = new haxe_Timer(2000); + this.isConnected = false; + this.personal = new Client("Unknown",0); + this.filters = []; + this.globalIp = ""; + this.pageTitle = window.document.title; + this.clients = []; + var _gthis = this; + this.player = new client_Player(this); + if(host == null) { + host = window.location.hostname; + } + if(host == "") { + host = "localhost"; + } + this.host = host; + this.initListeners(); + this.onTimeGet.run = function() { + if(_gthis.player.isListEmpty()) { + return; + } + _gthis.send({ type : "GetTime"}); + return; + }; + window.document.onvisibilitychange = function() { + if(!window.document.hidden && _gthis.onBlinkTab != null) { + window.document.title = _gthis.getPageTitle(); + _gthis.onBlinkTab.stop(); + _gthis.onBlinkTab = null; + } + return; + }; + Lang.init("langs",function() { + _gthis.openWebSocket(host,port); + return; + }); +}; +client_Main.__name__ = true; +client_Main.main = function() { + new client_Main(); +}; +client_Main.prototype = { + openWebSocket: function(host,port) { + var _gthis = this; + this.ws = new WebSocket("ws://" + host + ":" + port); + this.ws.onmessage = $bind(this,this.onMessage); + this.ws.onopen = function() { + _gthis.serverMessage(1); + return _gthis.isConnected = true; + }; + this.ws.onclose = function() { + if(_gthis.isConnected) { + _gthis.serverMessage(2); + } + _gthis.isConnected = false; + _gthis.player.pause(); + return haxe_Timer.delay(function() { + _gthis.openWebSocket(host,port); + return; + },2000); + }; + } + ,initListeners: function() { + var _gthis = this; + client_Buttons.init(this); + client_MobileView.init(); + window.document.querySelector("#leader_btn").onclick = function(e) { + _gthis.setLeaderButton((_gthis.personal.group & 2) == 0); + _gthis.send({ type : "SetLeader", setLeader : { clientName : (_gthis.personal.group & 2) != 0 ? "" : _gthis.personal.name}}); + return; + }; + window.document.querySelector("#queue_next").onclick = function(e1) { + _gthis.addVideoUrl(false); + return; + }; + window.document.querySelector("#queue_end").onclick = function(e2) { + _gthis.addVideoUrl(true); + return; + }; + window.document.querySelector("#mediaurl").onkeydown = function(e3) { + if(e3.keyCode == 13) { + _gthis.addVideoUrl(true); + } + }; + } + ,addVideoUrl: function(atEnd) { + var _gthis = this; + var mediaUrl = window.document.querySelector("#mediaurl"); + var url = mediaUrl.value; + if(url.length == 0) { + return; + } + mediaUrl.value = ""; + var _this_r = new RegExp(",(https?)","g".split("u").join("")); + var links = url.replace(_this_r,"|$1").split("|"); + this.addVideo(atEnd || this.player.isListEmpty() ? links.shift() : links.pop(),atEnd,function() { + _gthis.addVideoArray(links,atEnd); + return; + }); + } + ,addVideoArray: function(links,atEnd) { + var _gthis = this; + if(links.length == 0) { + return; + } + this.addVideo(atEnd ? links.shift() : links.pop(),atEnd,function() { + _gthis.addVideoArray(links,atEnd); + return; + }); + } + ,addVideo: function(url,atEnd,callback) { + var _gthis = this; + var protocol = window.location.protocol; + if(StringTools.startsWith(url,"/")) { + url = "" + protocol + "//" + window.location.hostname + ":" + window.location.port + url; + } + if(!StringTools.startsWith(url,"http")) { + url = "" + protocol + "//" + url; + } + var pos = url.lastIndexOf("/") + 1; + var name = HxOverrides.substr(url,pos,null); + var matchName = new EReg("^(.+)\\.",""); + if(matchName.match(name)) { + name = matchName.matched(1); + } else { + name = Lang.get("rawVideo"); + } + this.getRemoteVideoDuration(url,function(duration) { + _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}, atEnd : atEnd}}); + callback(); + return; + }); + } + ,refreshPlayer: function() { + this.player.refresh(); + } + ,getPlaylistLinks: function() { + var items = this.player.getItems(); + var _g = []; + var _g1 = 0; + while(_g1 < items.length) _g.push(items[_g1++].url); + return _g; + } + ,replaceLocalIp: function(url) { + if(this.host == this.globalIp) { + return url; + } + return StringTools.replace(url,this.globalIp,this.host); + } + ,getRemoteVideoDuration: function(src,callback) { + var player = window.document.querySelector("#ytapiplayer"); + var video = window.document.createElement("video"); + video.src = src; + video.onerror = function(e) { + callback(0); + return; + }; + video.onloadedmetadata = function() { + if(player.contains(video)) { + player.removeChild(video); + } + callback(video.duration); + return; + }; + client_Utils.prepend(player,video); + } + ,onMessage: function(e) { + var data = JSON.parse(e.data); + var t = data.type; + var t1 = t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 188, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + switch(data.type) { + case "AddVideo": + if(this.player.isListEmpty()) { + this.player.setVideo(data.addVideo.item); + } + this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); + break; + case "ClearChat": + window.document.querySelector("#messagebuffer").innerHTML = ""; + break; + case "ClearPlaylist": + this.player.clearItems(); + if(this.player.isListEmpty()) { + this.player.pause(); + } + break; + case "Connected": + this.onConnected(data); + this.onTimeGet.run(); + break; + case "GetTime": + var newTime = data.getTime.time; + var time = this.player.getTime(); + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { + if(Math.abs(time - newTime) < 2) { + return; + } + this.player.setTime(time,false); + return; + } + if(!data.getTime.paused) { + this.player.play(); + } else { + this.player.pause(); + } + if(Math.abs(time - newTime) < 2) { + return; + } + this.player.setTime(newTime); + break; + case "Login": + this.onLogin(data.login.clients,data.login.clientName); + break; + case "LoginError": + var text = StringTools.replace(Lang.get("usernameError"),"$MAX","" + this.config.maxLoginLength); + this.serverMessage(4,text); + break; + case "Logout": + this.updateClients(data.logout.clients); + this.personal = new Client(data.logout.clientName,0); + this.showGuestLoginPanel(); + break; + case "Message": + this.addMessage(data.message.clientName,data.message.text); + break; + case "Pause": + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { + return; + } + this.player.pause(); + this.player.setTime(data.pause.time); + break; + case "Play": + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { + return; + } + this.player.setTime(data.play.time); + this.player.play(); + break; + case "RemoveVideo": + this.player.removeItem(data.removeVideo.url); + if(this.player.isListEmpty()) { + this.player.pause(); + } + break; + case "Rewind": + this.player.setTime(data.rewind.time); + break; + case "SetLeader": + ClientTools.setLeader(this.clients,data.setLeader.clientName); + this.updateUserList(); + this.setLeaderButton((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0); + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { + this.player.setTime(this.player.getTime(),false); + } + break; + case "SetTime": + var newTime1 = data.setTime.time; + var time1 = this.player.getTime(); + if(Math.abs(time1 - newTime1) < 2) { + return; + } + this.player.setTime(newTime1); + break; + case "ShufflePlaylist": + break; + case "UpdateClients": + this.updateClients(data.updateClients.clients); + this.personal = ClientTools.getByName(this.clients,this.personal.name,this.personal); + break; + case "UpdatePlaylist": + this.player.setItems(data.updatePlaylist.videoList); + break; + case "VideoLoaded": + this.player.setTime(0); + this.player.play(); + break; + } + } + ,onConnected: function(data) { + var connected = data.connected; + this.globalIp = connected.globalIp; + this.setConfig(connected.config); + if(connected.isUnknownClient) { + this.updateClients(connected.clients); + this.personal = ClientTools.getByName(this.clients,connected.clientName,this.personal); + this.showGuestLoginPanel(); + } else { + this.onLogin(connected.clients,connected.clientName); + } + var guestName = window.document.querySelector("#guestname"); + if(guestName.value.length > 0) { + this.send({ type : "Login", login : { clientName : guestName.value}}); + } + var _g = 0; + var _g1 = connected.history; + while(_g < _g1.length) { + var message = _g1[_g]; + ++_g; + this.addMessage(message.name,message.text,message.time); + } + this.player.setItems(connected.videoList); + } + ,setConfig: function(config) { + this.config = config; + this.pageTitle = config.channelName; + window.document.querySelector("#guestname").maxLength = config.maxLoginLength; + window.document.querySelector("#chatline").maxLength = config.maxMessageLength; + this.filters.length = 0; + var _g = 0; + var _g1 = config.filters; + while(_g < _g1.length) { + var filter = _g1[_g]; + ++_g; + this.filters.push({ regex : new EReg(filter.regex,filter.flags), replace : filter.replace}); + } + var _g2 = 0; + var _g3 = config.emotes; + while(_g2 < _g3.length) { + var emote = _g3[_g2]; + ++_g2; + this.filters.push({ regex : new EReg(this.escapeRegExp(emote.name),"g"), replace : ""}); + } + var smilesWrap = window.document.querySelector("#smileswrap"); + smilesWrap.onclick = function(e) { + var el = e.target; + var form = window.document.querySelector("#chatline"); + form.value += " " + el.title; + form.focus(); + return; + }; + smilesWrap.innerHTML = ""; + var _g4 = 0; + var _g5 = config.emotes; + while(_g4 < _g5.length) { + var emote1 = _g5[_g4]; + ++_g4; + var img = window.document.createElement("img"); + img.className = "smile-preview"; + img.src = emote1.image; + img.title = emote1.name; + smilesWrap.appendChild(img); + } + } + ,onLogin: function(data,clientName) { + this.updateClients(data); + var newPersonal = ClientTools.getByName(this.clients,clientName); + if(newPersonal == null) { + return; + } + this.personal = newPersonal; + this.hideGuestLoginPanel(); + } + ,showGuestLoginPanel: function() { + window.document.querySelector("#guestlogin").style.display = "block"; + window.document.querySelector("#chatline").style.display = "none"; + } + ,hideGuestLoginPanel: function() { + window.document.querySelector("#guestlogin").style.display = "none"; + window.document.querySelector("#chatline").style.display = "block"; + } + ,updateClients: function(newClients) { + this.clients.length = 0; + var _g = 0; + while(_g < newClients.length) this.clients.push(Client.fromData(newClients[_g++])); + this.updateUserList(); + } + ,send: function(data) { + if(!this.isConnected) { + return; + } + this.ws.send(JSON.stringify(data)); + } + ,serverMessage: function(type,text) { + var msgBuf = window.document.querySelector("#messagebuffer"); + var div = window.document.createElement("div"); + var time = "[" + new Date().toTimeString().split(" ")[0] + "] "; + switch(type) { + case 1: + div.className = "server-msg-reconnect"; + div.innerHTML = Lang.get("msgConnected"); + break; + case 2: + div.className = "server-msg-disconnect"; + div.innerHTML = Lang.get("msgDisconnected"); + break; + case 3: + div.className = "server-whisper"; + div.innerHTML = time + text + " " + Lang.get("entered"); + break; + case 4: + div.className = "server-whisper"; + div.innerHTML = time + text; + break; + default: + } + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; + } + ,updateUserList: function() { + window.document.querySelector("#usercount").innerHTML = this.clients.length + " " + Lang.get("online"); + window.document.title = this.getPageTitle(); + var list_b = ""; + var _g = 0; + var _g1 = this.clients; + while(_g < _g1.length) { + var client1 = _g1[_g]; + ++_g; + list_b += "
    "; + if((client1.group & 2) != 0) { + list_b += ""; + } + list_b += Std.string("" + client1.name + "
    "); + } + window.document.querySelector("#userlist").innerHTML = list_b; + } + ,getPageTitle: function() { + return "" + this.pageTitle + " (" + this.clients.length + ")"; + } + ,addMessage: function(name,text,time) { + var _gthis = this; + var msgBuf = window.document.querySelector("#messagebuffer"); + var userDiv = window.document.createElement("div"); + userDiv.className = "chat-msg-" + name; + var tstamp = window.document.createElement("span"); + tstamp.className = "timestamp"; + if(time == null) { + time = "[" + new Date().toTimeString().split(" ")[0] + "] "; + } + tstamp.innerHTML = time; + var nameDiv = window.document.createElement("strong"); + nameDiv.className = "username"; + nameDiv.innerHTML = name + ": "; + var textDiv = window.document.createElement("span"); + if(StringTools.startsWith(text,"/")) { + if(name == this.personal.name) { + this.handleCommands(HxOverrides.substr(text,1,null)); + } + } else { + var _g = 0; + var _g1 = this.filters; + while(_g < _g1.length) { + var filter = _g1[_g]; + ++_g; + text = text.replace(filter.regex.r,filter.replace); + } + } + textDiv.innerHTML = text; + var isInChatEnd = msgBuf.scrollHeight - msgBuf.scrollTop == msgBuf.clientHeight; + userDiv.appendChild(tstamp); + userDiv.appendChild(nameDiv); + userDiv.appendChild(textDiv); + msgBuf.appendChild(userDiv); + if(isInChatEnd) { + while(msgBuf.children.length > 200) msgBuf.removeChild(msgBuf.firstChild); + msgBuf.scrollTop = msgBuf.scrollHeight; + } + if(name == this.personal.name) { + msgBuf.scrollTop = msgBuf.scrollHeight; + } + if(window.document.hidden && this.onBlinkTab == null) { + this.onBlinkTab = new haxe_Timer(1000); + this.onBlinkTab.run = function() { + if(StringTools.startsWith(window.document.title,_gthis.pageTitle)) { + return window.document.title = "*Chat*"; + } else { + return window.document.title = _gthis.getPageTitle(); + } + }; + this.onBlinkTab.run(); + } + } + ,handleCommands: function(text) { + if(text == "clear") { + if((this.personal.group & 4) != 0) { + this.send({ type : "ClearChat"}); + } + } + if(this.matchNumbers.match(text)) { + this.send({ type : "Rewind", rewind : { time : Std.parseInt(text)}}); + } + } + ,setLeaderButton: function(flag) { + var leaderBtn = window.document.querySelector("#leader_btn"); + if((this.personal.group & 2) != 0) { + leaderBtn.classList.add("label-success"); + } else { + leaderBtn.classList.remove("label-success"); + } + } + ,escapeRegExp: function(regex) { + var _this_r = new RegExp("([.*+?^${}()|[\\]\\\\])","g".split("u").join("")); + return regex.replace(_this_r,"\\$1"); + } +}; +var client_MobileView = function() { }; +client_MobileView.__name__ = true; +client_MobileView.init = function() { + var mvbtn = window.document.querySelector("#mv_btn"); + mvbtn.onclick = function(e) { + if(client_Utils.toggleFullScreen(window.document.documentElement)) { + window.document.body.classList.add("mobile-view"); + mvbtn.classList.add("active"); + var vwrap = window.document.querySelector("#videowrap"); + if(vwrap.children[0] == window.document.querySelector("currenttitle")) { + vwrap.appendChild(vwrap.children[0]); + } + } else { + window.document.body.classList.remove("mobile-view"); + mvbtn.classList.remove("active"); + var vwrap1 = window.document.querySelector("videowrap"); + if(vwrap1.children[0] != window.document.querySelector("currenttitle")) { + vwrap1.insertBefore(vwrap1.children[1],vwrap1.children[0]); + } + } + return; + }; +}; +var client_Player = function(main) { + this.skipSetTime = false; + this.isLoaded = false; + this.player = window.document.querySelector("#ytapiplayer"); + this.videoItemsEl = window.document.querySelector("#queue"); + this.items = []; + this.main = main; +}; +client_Player.__name__ = true; +client_Player.prototype = { + setVideo: function(item) { + var _gthis = this; + this.isLoaded = false; + this.video = window.document.createElement("video"); + this.video.id = "videoplayer"; + item.url = this.main.replaceLocalIp(item.url); + this.video.src = item.url; + this.video.controls = true; + this.video.oncanplaythrough = function(e) { + if(!_gthis.isLoaded) { + _gthis.main.send({ type : "VideoLoaded"}); + } + return _gthis.isLoaded = true; + }; + this.video.onseeking = function(e1) { + if(_gthis.skipSetTime) { + _gthis.skipSetTime = false; + return; + } + if((_gthis.main.personal.group & 2) == 0) { + return; + } + _gthis.main.send({ type : "SetTime", setTime : { time : _gthis.video.currentTime}}); + return; + }; + this.video.onpause = function(e2) { + if((_gthis.main.personal.group & 2) == 0) { + return; + } + _gthis.main.send({ type : "Pause", pause : { time : _gthis.video.currentTime}}); + return; + }; + this.video.onplay = function(e3) { + if((_gthis.main.personal.group & 2) == 0) { + return; + } + _gthis.main.send({ type : "Play", play : { time : _gthis.video.currentTime}}); + return; + }; + this.player.innerHTML = ""; + this.player.appendChild(this.video); + window.document.querySelector("#currenttitle").innerHTML = item.title; + } + ,addVideoItem: function(item,atEnd) { + var _gthis = this; + this.items.push(item); + var itemEl = this.nodeFromString("
  • \n\t\t\t\t" + item.title + "\n\t\t\t\t" + this.duration(item.duration) + "\n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
    \n\t\t\t
  • "); + itemEl.querySelector("#btn-delete").onclick = function(e) { + _gthis.main.send({ type : "RemoveVideo", removeVideo : { url : itemEl.querySelector(".qe_title").getAttribute("href")}}); + return; + }; + if(atEnd) { + this.videoItemsEl.appendChild(itemEl); + } else { + client_Utils.insertAtIndex(this.videoItemsEl,itemEl,1); + } + this.updateCounters(); + } + ,removeVideo: function() { + if(this.video == null) { + return; + } + this.player.removeChild(this.video); + this.video = null; + window.document.querySelector("#currenttitle").innerHTML = Lang.get("nothingPlaying"); + } + ,removeItem: function(url) { + var _g = 0; + var _g1 = this.videoItemsEl.children; + while(_g < _g1.length) { + var child = _g1[_g]; + ++_g; + if(child.querySelector(".qe_title").getAttribute("href") == url) { + this.videoItemsEl.removeChild(child); + break; + } + } + HxOverrides.remove(this.items,Lambda.find(this.items,function(item) { + return item.url == url; + })); + if(this.video.src == url) { + if(this.items.length > 0) { + this.setVideo(this.items[0]); + } + } + this.updateCounters(); + } + ,updateCounters: function() { + var tmp = "" + this.items.length + " "; + var tmp1 = Lang.get("videos"); + window.document.querySelector("#plcount").innerHTML = tmp + tmp1; + window.document.querySelector("#pllength").innerHTML = this.totalDuration(); + } + ,getItems: function() { + return this.items; + } + ,setItems: function(list) { + this.clearItems(); + if(list.length == 0) { + return; + } + if(this.video == null || this.video.src != list[0].url) { + this.setVideo(list[0]); + } + var _g = 0; + while(_g < list.length) this.addVideoItem(list[_g++],true); + } + ,clearItems: function() { + this.items.length = 0; + this.videoItemsEl.innerHTML = ""; + this.updateCounters(); + } + ,refresh: function() { + if(this.items.length == 0) { + return; + } + this.removeVideo(); + this.setVideo(this.items[0]); + } + ,duration: function(time) { + var h = time / 60 / 60 | 0; + var m = (time / 60 | 0) - h * 60; + var s = time % 60 | 0; + var time1 = "" + m + ":"; + if(m < 10) { + time1 = "0" + time1; + } + if(h > 0) { + time1 = "" + h + ":" + time1; + } + if(s < 10) { + time1 += "0"; + } + time1 += s; + return time1; + } + ,totalDuration: function() { + var time = 0.0; + var _g = 0; + var _g1 = this.items; + while(_g < _g1.length) time += _g1[_g++].duration; + return this.duration(time); + } + ,nodeFromString: function(div) { + var wrapper = window.document.createElement("div"); + wrapper.innerHTML = div; + return wrapper.firstElementChild; + } + ,isListEmpty: function() { + return this.items.length == 0; + } + ,pause: function() { + if(this.video == null) { + return; + } + this.video.pause(); + } + ,play: function() { + if(this.video == null) { + return; + } + this.video.play(); + } + ,setTime: function(time,isLocal) { + if(isLocal == null) { + isLocal = true; + } + if(this.video == null) { + return; + } + this.skipSetTime = isLocal; + this.video.currentTime = time; + } + ,getTime: function() { + if(this.video == null) { + return 0; + } + return this.video.currentTime; + } +}; +var client_Utils = function() { }; +client_Utils.__name__ = true; +client_Utils.prepend = function(parent,child) { + if(parent.firstChild == null) { + parent.appendChild(child); + } else { + parent.insertBefore(child,parent.firstChild); + } +}; +client_Utils.insertAtIndex = function(parent,child,i) { + if(i >= parent.children.length) { + parent.appendChild(child); + } else { + parent.insertBefore(child,parent.children[i]); + } +}; +client_Utils.toggleFullScreen = function(el) { + var state = true; + var doc = window.document; + var el2 = el; + if(window.document.fullscreenElement == null && doc.mozFullScreenElement == null && doc.webkitFullscreenElement == null) { + if(el.requestFullscreen != null) { + el.requestFullscreen(); + } else if(el2.mozRequestFullScreen != null) { + el2.mozRequestFullScreen(); + } else if(el2.webkitRequestFullscreen != null) { + el2.webkitRequestFullscreen(HTMLElement.ALLOW_KEYBOARD_INPUT); + } else { + state = false; + } + } else { + if(doc.cancelFullScreen != null) { + doc.cancelFullScreen(); + } else if(doc.mozCancelFullScreen != null) { + doc.mozCancelFullScreen(); + } else if(doc.webkitCancelFullScreen != null) { + doc.webkitCancelFullScreen(); + } + state = false; + } + return state; +}; +client_Utils.copyToClipboard = function(text) { + var clipboardData = window.clipboardData; + if(clipboardData != null && clipboardData.setData != null) { + clipboardData.setData("Text",text); + return; + } else if(window.document.queryCommandSupported != null) { + var textarea = window.document.createElement("textarea"); + textarea.textContent = text; + textarea.style.position = "fixed"; + window.document.body.appendChild(textarea); + textarea.select(); + window.document.execCommand("copy"); + window.document.body.removeChild(textarea); + } +}; +var haxe_Log = function() { }; +haxe_Log.__name__ = true; +haxe_Log.formatOutput = function(v,infos) { + var str = Std.string(v); + if(infos == null) { + return str; + } + var pstr = infos.fileName + ":" + infos.lineNumber; + if(infos.customParams != null) { + var _g = 0; + var _g1 = infos.customParams; + while(_g < _g1.length) str += ", " + Std.string(_g1[_g++]); + } + return pstr + ": " + str; +}; +haxe_Log.trace = function(v,infos) { + var str = haxe_Log.formatOutput(v,infos); + if(typeof(console) != "undefined" && console.log != null) { + console.log(str); + } +}; +var haxe_Timer = function(time_ms) { + var me = this; + this.id = setInterval(function() { + me.run(); + },time_ms); +}; +haxe_Timer.__name__ = true; +haxe_Timer.delay = function(f,time_ms) { + var t = new haxe_Timer(time_ms); + t.run = function() { + t.stop(); + f(); + }; + return t; +}; +haxe_Timer.prototype = { + stop: function() { + if(this.id == null) { + return; + } + clearInterval(this.id); + this.id = null; + } + ,run: function() { + } +}; +var haxe_http_HttpBase = function(url) { + this.url = url; + this.headers = []; + this.params = []; + this.emptyOnData = $bind(this,this.onData); +}; +haxe_http_HttpBase.__name__ = true; +haxe_http_HttpBase.prototype = { + onData: function(data) { + } + ,onBytes: function(data) { + } + ,onError: function(msg) { + } + ,onStatus: function(status) { + } + ,hasOnData: function() { + return !Reflect.compareMethods($bind(this,this.onData),this.emptyOnData); + } + ,success: function(data) { + this.responseBytes = data; + this.responseAsString = null; + if(this.hasOnData()) { + this.onData(this.get_responseData()); + } + this.onBytes(this.responseBytes); + } + ,get_responseData: function() { + if(this.responseAsString == null && this.responseBytes != null) { + this.responseAsString = this.responseBytes.getString(0,this.responseBytes.length,haxe_io_Encoding.UTF8); + } + return this.responseAsString; + } +}; +var haxe_http_HttpJs = function(url) { + this.async = true; + this.withCredentials = false; + haxe_http_HttpBase.call(this,url); +}; +haxe_http_HttpJs.__name__ = true; +haxe_http_HttpJs.__super__ = haxe_http_HttpBase; +haxe_http_HttpJs.prototype = $extend(haxe_http_HttpBase.prototype,{ + request: function(post) { + var _gthis = this; + this.responseAsString = null; + this.responseBytes = null; + var r = this.req = js_Browser.createXMLHttpRequest(); + var onreadystatechange = function(_) { + if(r.readyState != 4) { + return; + } + var s; + try { + s = r.status; + } catch( e ) { + s = null; + } + if(s == 0 && typeof(window) != "undefined") { + var protocol = window.location.protocol.toLowerCase(); + if(new EReg("^(?:about|app|app-storage|.+-extension|file|res|widget):$","").match(protocol)) { + s = r.response != null ? 200 : 404; + } + } + if(s == undefined) { + s = null; + } + if(s != null) { + _gthis.onStatus(s); + } + if(s != null && s >= 200 && s < 400) { + _gthis.req = null; + var onreadystatechange1 = haxe_io_Bytes.ofData(r.response); + _gthis.success(onreadystatechange1); + } else if(s == null) { + _gthis.req = null; + _gthis.onError("Failed to connect or resolve host"); + } else if(s == null) { + _gthis.req = null; + _gthis.responseBytes = haxe_io_Bytes.ofData(r.response); + _gthis.onError("Http Error #" + r.status); + } else { + switch(s) { + case 12007: + _gthis.req = null; + _gthis.onError("Unknown host"); + break; + case 12029: + _gthis.req = null; + _gthis.onError("Failed to connect to host"); + break; + default: + _gthis.req = null; + _gthis.responseBytes = haxe_io_Bytes.ofData(r.response); + _gthis.onError("Http Error #" + r.status); + } + } + }; + if(this.async) { + r.onreadystatechange = onreadystatechange; + } + var _g = this.postBytes; + var _g1 = this.postData; + var uri = _g1 == null ? _g == null ? null : new Blob([_g.b.bufferValue]) : _g == null ? _g1 : null; + if(uri != null) { + post = true; + } else { + var _g2 = 0; + var _g3 = this.params; + while(_g2 < _g3.length) { + var p = _g3[_g2]; + ++_g2; + if(uri == null) { + uri = ""; + } else { + uri = Std.string(uri) + "&"; + } + var s1 = p.name; + var value = Std.string(uri) + encodeURIComponent(s1) + "="; + var s2 = p.value; + uri = value + encodeURIComponent(s2); + } + } + try { + if(post) { + r.open("POST",this.url,this.async); + } else if(uri != null) { + r.open("GET",this.url + (this.url.split("?").length <= 1 ? "?" : "&") + Std.string(uri),this.async); + uri = null; + } else { + r.open("GET",this.url,this.async); + } + r.responseType = "arraybuffer"; + } catch( e1 ) { + var e2 = ((e1) instanceof js__$Boot_HaxeError) ? e1.val : e1; + this.req = null; + this.onError(e2.toString()); + return; + } + r.withCredentials = this.withCredentials; + if(!Lambda.exists(this.headers,function(h) { + return h.name == "Content-Type"; + }) && post && this.postData == null) { + r.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); + } + var _g21 = 0; + var _g31 = this.headers; + while(_g21 < _g31.length) { + var h1 = _g31[_g21]; + ++_g21; + r.setRequestHeader(h1.name,h1.value); + } + r.send(uri); + if(!this.async) { + onreadystatechange(null); + } + } +}); +var haxe_io_Bytes = function(data) { + this.length = data.byteLength; + this.b = new Uint8Array(data); + this.b.bufferValue = data; + data.hxBytes = this; + data.bytes = this.b; +}; +haxe_io_Bytes.__name__ = true; +haxe_io_Bytes.ofData = function(b) { + var hb = b.hxBytes; + if(hb != null) { + return hb; + } + return new haxe_io_Bytes(b); +}; +haxe_io_Bytes.prototype = { + getString: function(pos,len,encoding) { + if(pos < 0 || len < 0 || pos + len > this.length) { + throw new js__$Boot_HaxeError(haxe_io_Error.OutsideBounds); + } + if(encoding == null) { + encoding = haxe_io_Encoding.UTF8; + } + var s = ""; + var b = this.b; + var i = pos; + var max = pos + len; + switch(encoding._hx_index) { + case 0: + while(i < max) { + var c = b[i++]; + if(c < 128) { + if(c == 0) { + break; + } + s += String.fromCodePoint(c); + } else if(c < 224) { + var code = (c & 63) << 6 | b[i++] & 127; + s += String.fromCodePoint(code); + } else if(c < 240) { + var code1 = (c & 31) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; + s += String.fromCodePoint(code1); + } else { + var u = (c & 15) << 18 | (b[i++] & 127) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; + s += String.fromCodePoint(u); + } + } + break; + case 1: + while(i < max) { + var c1 = b[i++] | b[i++] << 8; + s += String.fromCodePoint(c1); + } + break; + } + return s; + } +}; +var haxe_io_Encoding = $hxEnums["haxe.io.Encoding"] = { __ename__ : true, __constructs__ : ["UTF8","RawNative"] + ,UTF8: {_hx_index:0,__enum__:"haxe.io.Encoding",toString:$estr} + ,RawNative: {_hx_index:1,__enum__:"haxe.io.Encoding",toString:$estr} +}; +var haxe_io_Error = $hxEnums["haxe.io.Error"] = { __ename__ : true, __constructs__ : ["Blocked","Overflow","OutsideBounds","Custom"] + ,Blocked: {_hx_index:0,__enum__:"haxe.io.Error",toString:$estr} + ,Overflow: {_hx_index:1,__enum__:"haxe.io.Error",toString:$estr} + ,OutsideBounds: {_hx_index:2,__enum__:"haxe.io.Error",toString:$estr} + ,Custom: ($_=function(e) { return {_hx_index:3,e:e,__enum__:"haxe.io.Error",toString:$estr}; },$_.__params__ = ["e"],$_) +}; +var haxe_io_Path = function(path) { + switch(path) { + case ".":case "..": + this.dir = path; + this.file = ""; + return; + } + var c1 = path.lastIndexOf("/"); + var c2 = path.lastIndexOf("\\"); + if(c1 < c2) { + this.dir = HxOverrides.substr(path,0,c2); + path = HxOverrides.substr(path,c2 + 1,null); + this.backslash = true; + } else if(c2 < c1) { + this.dir = HxOverrides.substr(path,0,c1); + path = HxOverrides.substr(path,c1 + 1,null); + } else { + this.dir = null; + } + var cp = path.lastIndexOf("."); + if(cp != -1) { + this.ext = HxOverrides.substr(path,cp + 1,null); + this.file = HxOverrides.substr(path,0,cp); + } else { + this.ext = null; + this.file = path; + } +}; +haxe_io_Path.__name__ = true; +haxe_io_Path.withoutExtension = function(path) { + var s = new haxe_io_Path(path); + s.ext = null; + return s.toString(); +}; +haxe_io_Path.prototype = { + toString: function() { + return (this.dir == null ? "" : this.dir + (this.backslash ? "\\" : "/")) + this.file + (this.ext == null ? "" : "." + this.ext); + } +}; +var js__$Boot_HaxeError = function(val) { + Error.call(this); + this.val = val; + if(Error.captureStackTrace) { + Error.captureStackTrace(this,js__$Boot_HaxeError); + } +}; +js__$Boot_HaxeError.__name__ = true; +js__$Boot_HaxeError.__super__ = Error; +js__$Boot_HaxeError.prototype = $extend(Error.prototype,{ +}); +var js_Boot = function() { }; +js_Boot.__name__ = true; +js_Boot.__string_rec = function(o,s) { + if(o == null) { + return "null"; + } + if(s.length >= 5) { + return "<...>"; + } + var t = typeof(o); + if(t == "function" && (o.__name__ || o.__ename__)) { + t = "object"; + } + switch(t) { + case "function": + return ""; + case "object": + if(o.__enum__) { + var e = $hxEnums[o.__enum__]; + var n = e.__constructs__[o._hx_index]; + var con = e[n]; + if(con.__params__) { + s = s + "\t"; + return n + "(" + ((function($this) { + var $r; + var _g = []; + { + var _g1 = 0; + var _g2 = con.__params__; + while(true) { + if(!(_g1 < _g2.length)) { + break; + } + var p = _g2[_g1]; + _g1 = _g1 + 1; + _g.push(js_Boot.__string_rec(o[p],s)); + } + } + $r = _g; + return $r; + }(this))).join(",") + ")"; + } else { + return n; + } + } + if(((o) instanceof Array)) { + var str = "["; + s += "\t"; + var _g3 = 0; + var _g11 = o.length; + while(_g3 < _g11) { + var i = _g3++; + str += (i > 0 ? "," : "") + js_Boot.__string_rec(o[i],s); + } + str += "]"; + return str; + } + var tostr; + try { + tostr = o.toString; + } catch( e1 ) { + var e2 = ((e1) instanceof js__$Boot_HaxeError) ? e1.val : e1; + return "???"; + } + if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") { + var s2 = o.toString(); + if(s2 != "[object Object]") { + return s2; + } + } + var str1 = "{\n"; + s += "\t"; + var hasp = o.hasOwnProperty != null; + var k = null; + for( k in o ) { + if(hasp && !o.hasOwnProperty(k)) { + continue; + } + if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") { + continue; + } + if(str1.length != 2) { + str1 += ", \n"; + } + str1 += s + k + " : " + js_Boot.__string_rec(o[k],s); + } + s = s.substring(1); + str1 += "\n" + s + "}"; + return str1; + case "string": + return o; + default: + return String(o); + } +}; +var js_Browser = function() { }; +js_Browser.__name__ = true; +js_Browser.createXMLHttpRequest = function() { + if(typeof XMLHttpRequest != "undefined") { + return new XMLHttpRequest(); + } + if(typeof ActiveXObject != "undefined") { + return new ActiveXObject("Microsoft.XMLHTTP"); + } + throw new js__$Boot_HaxeError("Unable to create XMLHttpRequest object."); +}; +function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else return o.iterator(); } +function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; } +$global.$haxeUID |= 0; +var __map_reserved = {}; +if( String.fromCodePoint == null ) String.fromCodePoint = function(c) { return c < 0x10000 ? String.fromCharCode(c) : String.fromCharCode((c>>10)+0xD7C0)+String.fromCharCode((c&0x3FF)+0xDC00); } +String.__name__ = true; +Array.__name__ = true; +Object.defineProperty(js__$Boot_HaxeError.prototype,"message",{ get : function() { + return String(this.val); +}}); +js_Boot.__toStr = ({ }).toString; +Lang.ids = ["en","ru"]; +Lang.langs = new haxe_ds_StringMap(); +Lang.lang = HxOverrides.substr(window.navigator.language,0,2).toLowerCase(); +client_Buttons.personalHistory = []; +client_Buttons.personalHistoryId = -1; +client_Main.main(); +})(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/res/css/custom.css b/res/css/custom.css index e69de29..05b94fa 100644 --- a/res/css/custom.css +++ b/res/css/custom.css @@ -0,0 +1,4 @@ +/* + Create user/res/css/custom.css + in project folder to override this file. +*/ diff --git a/src/Client.hx b/src/Client.hx index 7aa14c5..4604ab7 100644 --- a/src/Client.hx +++ b/src/Client.hx @@ -1,9 +1,8 @@ package; #if nodejs +import js.node.http.IncomingMessage; import js.npm.ws.WebSocket; -#elseif js -import js.html.WebSocket; #end import haxe.EnumFlags; @@ -23,15 +22,21 @@ class Client { #if nodejs public final ws:WebSocket; public final id:Int; + public final req:IncomingMessage; #end public var name:String; public var group:EnumFlags; public var isLeader(get, set):Bool; public var isAdmin(get, set):Bool; - public function new(?ws:WebSocket, ?id:Int, name:String, group:Int) { + #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; diff --git a/src/Types.hx b/src/Types.hx index f2b506f..5706111 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -46,7 +46,8 @@ typedef WsEvent = { clients:Array, isUnknownClient:Bool, clientName:String, - videoList:Array + videoList:Array, + globalIp:String }, ?login:{ clientName:String, diff --git a/src/client/Main.hx b/src/client/Main.hx index 96353f8..432a095 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -20,6 +20,8 @@ class Main { final clients:Array = []; var pageTitle = document.title; + final host:String; + var globalIp = ""; var config:Null; final filters:Array<{regex:EReg, replace:String}> = []; var personal = new Client("Unknown", 0); @@ -35,9 +37,13 @@ class Main { player = new Player(this); if (host == null) host = Browser.location.hostname; if (host == "") host = "localhost"; + this.host = host; initListeners(); - onTimeGet.run = () -> send({type: GetTime}); + onTimeGet.run = () -> { + if (player.isListEmpty()) return; + send({type: GetTime}); + } document.onvisibilitychange = () -> { if (!document.hidden && onBlinkTab != null) { document.title = getPageTitle(); @@ -117,7 +123,13 @@ class Main { } function addVideo(url:String, atEnd:Bool, callback:()->Void):Void { - if (!url.startsWith("http")) url = '${Browser.location.protocol}//$url'; + final protocol = Browser.location.protocol; + if (url.startsWith("/")) { + final host = Browser.location.hostname; + final port = Browser.location.port; + url = '$protocol//$host:$port$url'; + } + if (!url.startsWith("http")) url = '$protocol//$url'; var name = url.substr(url.lastIndexOf('/') + 1); final matchName = ~/^(.+)\./; if (matchName.match(name)) name = matchName.matched(1); @@ -149,6 +161,11 @@ class Main { ]; } + public function tryLocalIp(url:String):String { + if (host == globalIp) return url; + return url.replace(globalIp, host); + } + function getRemoteVideoDuration(src:String, callback:(duration:Float)->Void):Void { final player:Element = ge("#ytapiplayer"); final video = document.createVideoElement(); @@ -173,38 +190,49 @@ class Main { case Connected: onConnected(data); onTimeGet.run(); + case Login: onLogin(data.login.clients, data.login.clientName); + case LoginError: final text = Lang.get("usernameError") .replace("$MAX", '${config.maxLoginLength}'); serverMessage(4, text); + case Logout: updateClients(data.logout.clients); personal = new Client(data.logout.clientName, 0); showGuestLoginPanel(); + case UpdateClients: updateClients(data.updateClients.clients); personal = clients.getByName(personal.name, personal); + case Message: addMessage(data.message.clientName, data.message.text); + case AddVideo: if (player.isListEmpty()) player.setVideo(data.addVideo.item); player.addVideoItem(data.addVideo.item, data.addVideo.atEnd); + case VideoLoaded: player.setTime(0); player.play(); + case RemoveVideo: player.removeItem(data.removeVideo.url); if (player.isListEmpty()) player.pause(); + case Pause: if (isLeader()) return; player.pause(); player.setTime(data.pause.time); + case Play: if (isLeader()) return; player.setTime(data.play.time); player.play(); + case GetTime: final newTime = data.getTime.time; final time = player.getTime(); @@ -219,23 +247,29 @@ class Main { else player.pause(); if (Math.abs(time - newTime) < 2) return; player.setTime(newTime); + case SetTime: final newTime = data.setTime.time; final time = player.getTime(); if (Math.abs(time - newTime) < 2) return; player.setTime(newTime); + case Rewind: player.setTime(data.rewind.time); + case SetLeader: clients.setLeader(data.setLeader.clientName); updateUserList(); setLeaderButton(isLeader()); if (isLeader()) player.setTime(player.getTime(), false); + case ClearChat: ge("#messagebuffer").innerHTML = ""; + case ClearPlaylist: player.clearItems(); if (player.isListEmpty()) player.pause(); + case ShufflePlaylist: // server-only case UpdatePlaylist: player.setItems(data.updatePlaylist.videoList); @@ -244,6 +278,7 @@ class Main { function onConnected(data:WsEvent):Void { final connected = data.connected; + globalIp = connected.globalIp; setConfig(connected.config); if (connected.isUnknownClient) { updateClients(connected.clients); diff --git a/src/client/Player.hx b/src/client/Player.hx index 34ef716..ca3d78f 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -25,6 +25,7 @@ class Player { isLoaded = false; video = document.createVideoElement(); video.id = "videoplayer"; + item.url = main.tryLocalIp(item.url); video.src = item.url; video.controls = true; video.oncanplaythrough = e -> { diff --git a/src/server/HttpServer.hx b/src/server/HttpServer.hx index b49301b..a5b752b 100644 --- a/src/server/HttpServer.hx +++ b/src/server/HttpServer.hx @@ -1,12 +1,11 @@ package server; +import sys.FileSystem; import js.node.Buffer; import haxe.io.Path; import js.node.Fs; -import sys.io.File; import js.node.http.IncomingMessage; import js.node.http.ServerResponse; -import js.Node.__dirname; import js.node.Path as JsPath; using StringTools; @@ -33,18 +32,31 @@ class HttpServer { ]; static var dir:String; + static var customDir:String; + static var hasCustomRes = false; + static var allowedLocalFiles:Map = []; - public static function init(directory:String):Void { - dir = directory; + public static function init(dir:String, ?customDir:String):Void { + HttpServer.dir = dir; + if (customDir == null) return; + HttpServer.customDir = customDir; + hasCustomRes = FileSystem.exists(customDir); } public static function serveFiles(req:IncomingMessage, res:ServerResponse):Void { - var filePath = dir + req.url; - if (req.url == "/") filePath = '$dir/index.html'; + var url = req.url; + if (url == "/") url = "/index.html"; + var filePath = dir + url; final extension = Path.extension(filePath).toLowerCase(); final contentType = getMimeType(extension); + if (req.connection.remoteAddress == req.connection.localAddress + || allowedLocalFiles[url]) { + final isExists = serveLocalFile(res, url, extension, contentType); + if (isExists) return; + } + if (!isChildOf(dir, filePath)) { res.statusCode = 500; var rel = JsPath.relative(dir, filePath); @@ -52,21 +64,14 @@ class HttpServer { return; } - // load client code from build folder - if (filePath == '$dir/client.js') { - filePath = '$__dirname/client.js'; + if (hasCustomRes) { + final path = customDir + url; + if (Fs.existsSync(path)) filePath = path; } - Fs.readFile(filePath, function(err:Dynamic, data:Buffer) { + Fs.readFile(filePath, (err:Dynamic, data:Buffer) -> { if (err != null) { - if (err.code == "ENOENT") { - res.statusCode = 404; - var rel = JsPath.relative(dir, filePath); - res.end('File $rel not found.'); - } else { - res.statusCode = 500; - res.end('Error getting the file: $err.'); - } + readFileError(err, res, filePath); return; } res.setHeader("Content-Type", contentType); @@ -78,13 +83,40 @@ class HttpServer { }); } + static function readFileError(err:Dynamic, res:ServerResponse, filePath:String):Void { + if (err.code == "ENOENT") { + res.statusCode = 404; + var rel = JsPath.relative(dir, filePath); + res.end('File $rel not found.'); + } else { + res.statusCode = 500; + res.end('Error getting the file: $err.'); + } + } + + static function serveLocalFile(res:ServerResponse, filePath:String, ext:String, contentType:String):Bool { + if (ext != "mp4" && ext != "mp3" && ext != "wav") return false; + if (!Fs.existsSync(filePath)) return false; + allowedLocalFiles[filePath] = true; + Fs.readFile(filePath, (err:Dynamic, data:Buffer) -> { + if (err != null) { + readFileError(err, res, filePath); + return; + } + res.setHeader("Content-Type", contentType); + res.end(data); + }); + return true; + } + static final matchLang = ~/^[A-z]+/; + static final matchVarString = ~/\${([A-z_]+)}/g; static function localizeHtml(data:String, lang:String):String { if (lang != null && matchLang.match(lang)) { lang = matchLang.matched(0); } else lang = "en"; - data = ~/\${([A-z_]+)}/g.map(data, (regExp) -> { + data = matchVarString.map(data, (regExp) -> { final key = regExp.matched(1); return Lang.get(lang, key); }); @@ -92,14 +124,13 @@ class HttpServer { } static function isChildOf(parent:String, child:String):Bool { - final path = JsPath; - final relative = path.relative(parent, child); - return relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative); + final rel = JsPath.relative(parent, child); + return rel.length > 0 && !rel.startsWith('..') && !JsPath.isAbsolute(rel); } static function getMimeType(ext:String):String { - var contentType = mimeTypes[ext]; - if (contentType == null) contentType = "application/octet-stream"; + final contentType = mimeTypes[ext]; + if (contentType == null) return "application/octet-stream"; return contentType; } diff --git a/src/server/Main.hx b/src/server/Main.hx index e2b9b18..442abc8 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -10,8 +10,10 @@ 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 Types; +using StringTools; using ClientTools; using Lambda; @@ -19,6 +21,9 @@ class Main { final rootDir = '$__dirname/..'; final wss:WSServer; + final localIp:String; + var globalIp:String; + final port:Int; final config:Config; final clients:Array = []; final freeIds:Array = []; @@ -33,25 +38,31 @@ class Main { wss = new WSServer({port: wsPort}); wss.on("connection", onConnect); function exit() { + // TODO save state process.exit(); } process.on("exit", exit); process.on("SIGINT", exit); // ctrl+c + process.on("SIGUSR1", exit); // kill pid + process.on("SIGUSR2", exit); process.on("uncaughtException", (log) -> { trace(log); }); process.on("unhandledRejection", (reason, promise) -> { trace("Unhandled Rejection at:", reason); }); + localIp = Utils.getLocalIp(); + globalIp = localIp; + this.port = port; Utils.getGlobalIp(ip -> { - final local = Utils.getLocalIp(); - trace('Local: http://$local:$port'); - trace('Global: http://$ip:$port'); + globalIp = ip; + trace('Local: http://$localIp:$port'); + trace('Global: http://$globalIp:$port'); }); final dir = '$rootDir/res'; - HttpServer.init(dir); + HttpServer.init(dir, '$rootDir/user/res'); Lang.init('$dir/langs'); Http.createServer((req, res) -> { @@ -61,7 +72,7 @@ class Main { function getUserConfig():Config { final config:Config = Json.parse(File.getContent('$rootDir/default-config.json')); - final customPath = '$rootDir/config.json'; + final customPath = '$rootDir/user/config.json'; if (!FileSystem.exists(customPath)) return config; final customConfig:Config = Json.parse(File.getContent(customPath)); for (field in Reflect.fields(customConfig)) { @@ -71,13 +82,13 @@ class Main { return config; } - function onConnect(ws:WebSocket, req):Void { + function onConnect(ws:WebSocket, req:IncomingMessage):Void { final ip = req.connection.remoteAddress; final id = freeIds.length > 0 ? freeIds.shift() : clients.length; final name = 'Guest ${id + 1}'; trace('$name connected ($ip)'); final isAdmin = req.connection.localAddress == ip; - final client = new Client(ws, id, name, 0); + final client = new Client(ws, req, id, name, 0); if (isAdmin) client.group.set(Admin); clients.push(client); if (clients.length == 1 && videoList.length > 0) @@ -93,7 +104,8 @@ class Main { clients: [ for (client in clients) client.getData() ], - videoList: videoList + videoList: videoList, + globalIp: globalIp } }); sendClientList(); @@ -103,7 +115,7 @@ class Main { }); ws.on("close", err -> { trace('Client ${client.name} disconnected'); - sortedPush(freeIds, client.id); + Utils.sortedPush(freeIds, client.id); clients.remove(client); sendClientList(); if (client.isLeader) { @@ -116,17 +128,6 @@ class Main { }); } - function sortedPush(ids:Array, id:Int):Void { - for (i in 0...ids.length) { - final n = ids[i]; - if (id < n) { - ids.insert(i, id); - return; - } - } - ids.push(id); - } - function onMessage(client:Client, data:WsEvent):Void { switch (data.type) { case Connected: @@ -149,6 +150,7 @@ class Main { } }); sendClientList(); + case LoginError: case Logout: final oldName = client.name; @@ -163,6 +165,7 @@ class Main { } }); sendClientList(); + case Message: var text = data.message.text; if (text.length == 0) return; @@ -175,15 +178,23 @@ class Main { messages.push({text: text, name: client.name, time: time}); if (messages.length > config.serverChatHistory) messages.shift(); broadcast(data); + case AddVideo: - if (data.addVideo.atEnd) videoList.push(data.addVideo.item); - else videoList.insert(1, data.addVideo.item); + final item = data.addVideo.item; + final localOrigin = '$localIp:$port'; + if (item.url.indexOf(localOrigin) != -1) { + item.url = item.url.replace(localOrigin, '$globalIp:$port'); + } + if (data.addVideo.atEnd) videoList.push(item); + else videoList.insert(1, item); broadcast(data); // Initial timer start if VideoLoaded is not happen if (videoList.length == 1) restartWaitTimer(); + case VideoLoaded: // Called if client loads next video and can play it prepareVideoPlayback(); + case RemoveVideo: if (videoList.length == 0) return; final url = data.removeVideo.url; @@ -193,16 +204,19 @@ class Main { ); broadcast(data); if (videoList.length > 0) restartWaitTimer(); + case Pause: if (videoList.length == 0) return; if (!client.isLeader) return; videoTimer.pause(); broadcast(data); + case Play: if (videoList.length == 0) return; if (!client.isLeader) return; videoTimer.play(); broadcast(data); + case GetTime: if (videoList.length == 0) return; if (videoTimer.getTime() > videoList[0].duration) { @@ -220,11 +234,13 @@ class Main { time: videoTimer.getTime(), paused: videoTimer.isPaused() }}); + case SetTime: if (videoList.length == 0) return; if (!client.isLeader) return; videoTimer.setTime(data.setTime.time); broadcastExcept(client, data); + case Rewind: if (videoList.length == 0) return; // TODO permission @@ -232,6 +248,7 @@ class Main { if (data.rewind.time < 0) data.rewind.time = 0; videoTimer.setTime(data.rewind.time); broadcast(data); + case SetLeader: clients.setLeader(data.setLeader.clientName); broadcast({ @@ -248,12 +265,15 @@ class Main { } }); } + case ClearChat: if (client.isAdmin) broadcast(data); + case ClearPlaylist: videoTimer.stop(); videoList.resize(0); broadcast(data); + case ShufflePlaylist: if (videoList.length == 0) return; final first = videoList.shift(); @@ -262,7 +282,7 @@ class Main { broadcast({type: UpdatePlaylist, updatePlaylist: { videoList: videoList }}); - case UpdatePlaylist: + case UpdatePlaylist: // client-only } } diff --git a/src/server/Utils.hx b/src/server/Utils.hx index 2ecbd42..22ddc77 100644 --- a/src/server/Utils.hx +++ b/src/server/Utils.hx @@ -28,6 +28,17 @@ class Utils { return "127.0.0.1"; } + public static function sortedPush(ids:Array, id:Int):Void { + for (i in 0...ids.length) { + final n = ids[i]; + if (id < n) { + ids.insert(i, id); + return; + } + } + ids.push(id); + } + public static function shuffle(arr:Array):Void { for (i in 0...arr.length) { final n = Std.random(arr.length); diff --git a/user/README.md b/user/README.md new file mode 100644 index 0000000..f59e576 --- /dev/null +++ b/user/README.md @@ -0,0 +1,18 @@ +## User-specific config +You can create `config.json` file in this folder to override `default-config.json` options. +All root config fields are optional to override, so you need to create only what you want to change. +File example: +```json +{ + "channelName": "-=SuperChannel=-", + "videoLimit": 10, +} +``` +## User-specific resources +You can patch any file you want in project `res/` by creating `user/res/sameName` files. +For example `user/res/index.html` or `user/res/css/custom.css`. +You can also add any new files. +## Other files here +- `state.json` - saved state of latest server session (messages, videos, video time). +- `logs/` - latest 10 logs. You can change count in config. +- `crashes/` - folder with latest error logs, when the server had to restart itself. -- cgit v1.2.3