From ec7e4b5ad120828f1464cf4186287d4928b462a8 Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 28 Feb 2020 19:55:32 +0300 Subject: Youtube player support --- build-client.hxml | 1 + build-server.hxml | 2 + build/server.js | 4 +- res/client.js | 217 ++++++++++++++++++++++++++++++++++-------- src/client/IPlayer.hx | 1 + src/client/Main.hx | 18 +--- src/client/Player.hx | 21 ++-- src/client/players/Raw.hx | 17 +++- src/client/players/Youtube.hx | 145 ++++++++++++++++++++++++++++ src/server/Main.hx | 4 +- 10 files changed, 362 insertions(+), 68 deletions(-) create mode 100644 src/client/players/Youtube.hx diff --git a/build-client.hxml b/build-client.hxml index f4eca40..7c499bf 100644 --- a/build-client.hxml +++ b/build-client.hxml @@ -1,3 +1,4 @@ +-lib youtubeIFramePlayer -cp src --main client.Main -D analyzer-optimize diff --git a/build-server.hxml b/build-server.hxml index 220be8e..bcf8611 100644 --- a/build-server.hxml +++ b/build-server.hxml @@ -1,5 +1,7 @@ -lib hxnodejs -lib hxnodejs-ws +# Client libs for completion +-lib youtubeIFramePlayer -cp src --main server.Main -D analyzer-optimize diff --git a/build/server.js b/build/server.js index c90a298..0a90ca2 100644 --- a/build/server.js +++ b/build/server.js @@ -867,11 +867,11 @@ server_Main.prototype = { switch(data.type) { case "AddVideo": var item = data.addVideo.item; + item.author = client.name; var local = "" + this.localIp + ":" + this.port; if(item.url.indexOf(local) != -1) { item.url = StringTools.replace(item.url,local,"" + this.globalIp + ":" + this.port); } - item.author = client.name; if(Lambda.exists(this.videoList,function(i) { return i.url == item.url; })) { @@ -952,6 +952,7 @@ server_Main.prototype = { if((client.group & 2) == 0) { return; } + this.videoTimer.setTime(data.pause.time); this.videoTimer.pause(); this.broadcast(data); break; @@ -962,6 +963,7 @@ server_Main.prototype = { if((client.group & 2) == 0) { return; } + this.videoTimer.setTime(data.play.time); this.videoTimer.play(); this.broadcast(data); break; diff --git a/res/client.js b/res/client.js index 3367bcf..14e7ca4 100644 --- a/res/client.js +++ b/res/client.js @@ -779,15 +779,14 @@ client_Main.prototype = { if(!StringTools.startsWith(url,"http")) { url = "" + protocol + "//" + url; } - var pos = url.lastIndexOf("/") + 1; - var name = HxOverrides.substr(url,pos,null); + var name = HxOverrides.substr(url,url.lastIndexOf("/") + 1,null); var matchName = new EReg("^(.+)\\.",""); if(matchName.match(name)) { name = matchName.matched(1); } else { name = Lang.get("rawVideo"); } - this.getRemoteVideoDuration(url,function(duration) { + this.player.getRemoteDuration(url,function(duration) { if(duration == 0) { var tmp = Lang.get("addVideoError"); _gthis.serverMessage(4,tmp); @@ -822,31 +821,11 @@ client_Main.prototype = { } 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) { - if(player.contains(video)) { - player.removeChild(video); - } - 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 : 228, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 212, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); @@ -1019,9 +998,8 @@ client_Main.prototype = { } 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.value += " " + e.target.title; form.focus(); return; }; @@ -1224,7 +1202,6 @@ client_MobileView.init = function() { }; }; var client_Player = function(main) { - this.matchYoutube = new EReg("v=([A-z0-9_-]+)",""); this.skipSetTime = false; this.isLoaded = false; this.itemPos = 0; @@ -1271,21 +1248,18 @@ client_Player.prototype = { ,setPlayer: function(player) { this.player = player; } - ,isYoutube: function(url) { - if(url.indexOf("youtube.com/") == -1) { - return false; - } - if(url.indexOf("youtu.be/") == -1) { - return false; - } - if(!this.matchYoutube.match(url)) { - return false; + ,getRemoteDuration: function(url,callback) { + if(client_players_Youtube.isYoutube(url)) { + new client_players_Youtube(this.main,this).getRemoteDuration(url,callback); + } else { + new client_players_Raw(this.main,this).getRemoteDuration(url,callback); } - return true; } ,setVideo: function(i) { var item = this.items[i]; - if(!this.isYoutube(item.url)) { + if(client_players_Youtube.isYoutube(item.url)) { + this.setPlayer(new client_players_Youtube(this.main,this)); + } else { this.setPlayer(new client_players_Raw(this.main,this)); } var childs = this.videoItemsEl.children; @@ -1577,7 +1551,27 @@ var client_players_Raw = function(main,player) { }; client_players_Raw.__name__ = true; client_players_Raw.prototype = { - loadVideo: function(item) { + getRemoteDuration: function(src,callback) { + var _gthis = this; + var video = window.document.createElement("video"); + video.src = src; + video.onerror = function(e) { + if(_gthis.playerEl.contains(video)) { + _gthis.playerEl.removeChild(video); + } + callback(0); + return; + }; + video.onloadedmetadata = function() { + if(_gthis.playerEl.contains(video)) { + _gthis.playerEl.removeChild(video); + } + callback(video.duration); + return; + }; + client_Utils.prepend(this.playerEl,video); + } + ,loadVideo: function(item) { var _gthis = this; this.video = window.document.createElement("video"); this.video.id = "videoplayer"; @@ -1635,6 +1629,133 @@ client_players_Raw.prototype = { this.video.currentTime = time; } }; +var client_players_Youtube = function(main,player) { + this.isLoaded = false; + this.playerEl = window.document.querySelector("#ytapiplayer"); + this.main = main; + this.player = player; +}; +client_players_Youtube.__name__ = true; +client_players_Youtube.isYoutube = function(url) { + if(url.indexOf("youtu.be/") != -1) { + return client_players_Youtube.matchShort.match(url); + } + if(url.indexOf("youtube.com/embed/") != -1) { + return client_players_Youtube.matchEmbed.match(url); + } + if(url.indexOf("youtube.com/") == -1) { + return false; + } + return client_players_Youtube.matchId.match(url); +}; +client_players_Youtube.prototype = { + extractVideoId: function(url) { + if(url.indexOf("youtu.be/") != -1) { + client_players_Youtube.matchShort.match(url); + return client_players_Youtube.matchShort.matched(1); + } + if(url.indexOf("youtube.com/embed/") != -1) { + client_players_Youtube.matchEmbed.match(url); + return client_players_Youtube.matchEmbed.matched(1); + } + client_players_Youtube.matchId.match(url); + return client_players_Youtube.matchId.matched(1); + } + ,getRemoteDuration: function(url,callback) { + var _gthis = this; + if(!js_youtube_Youtube.isLoadedAPI) { + js_youtube_Youtube.init(function() { + _gthis.getRemoteDuration(url,callback); + return; + }); + return; + } + var video = window.document.createElement("div"); + video.id = "temp-videoplayer"; + client_Utils.prepend(this.playerEl,video); + this.youtube = new YT.Player(video.id,{ videoId : this.extractVideoId(url), playerVars : { modestbranding : 1, rel : 0, showinfo : 0}, events : { onReady : function(e) { + if(_gthis.playerEl.contains(video)) { + _gthis.playerEl.removeChild(video); + } + var tmp = _gthis.youtube.getDuration(); + callback(tmp); + return; + }, onError : function(e1) { + haxe_Log.trace("Error " + e1.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 74, className : "client.players.Youtube", methodName : "getRemoteDuration"}); + if(_gthis.playerEl.contains(video)) { + _gthis.playerEl.removeChild(video); + } + callback(0); + return; + }}}); + } + ,loadVideo: function(item) { + var _gthis = this; + if(!js_youtube_Youtube.isLoadedAPI) { + js_youtube_Youtube.init(function() { + _gthis.loadVideo(item); + return; + }); + return; + } + this.video = window.document.createElement("div"); + this.video.id = "videoplayer"; + this.playerEl.appendChild(this.video); + this.youtube = new YT.Player(this.video.id,{ videoId : this.extractVideoId(item.url), playerVars : { autoplay : 1, modestbranding : 1, rel : 0, showinfo : 0}, events : { onStateChange : function(e) { + switch(e.data) { + case -1: + _gthis.isLoaded = true; + _gthis.player.onCanBePlayed(); + break; + case 0: + break; + case 1: + _gthis.player.onPlay(); + break; + case 2: + _gthis.player.onPause(); + break; + case 3: + _gthis.player.onSetTime(); + break; + case 5: + break; + } + return; + }}}); + } + ,removeVideo: function() { + if(this.video == null) { + return; + } + this.playerEl.removeChild(this.video); + this.video = null; + } + ,play: function() { + if(!this.isLoaded) { + return; + } + this.youtube.playVideo(); + } + ,pause: function() { + if(!this.isLoaded) { + return; + } + this.youtube.pauseVideo(); + } + ,getTime: function() { + if(!this.isLoaded) { + return 0; + } + return this.youtube.getCurrentTime(); + } + ,setTime: function(time) { + if(!this.isLoaded) { + return; + } + this.youtube.seekTo(time,true); + } +}; var haxe_Log = function() { }; haxe_Log.__name__ = true; haxe_Log.formatOutput = function(v,infos) { @@ -2063,6 +2184,20 @@ js_Browser.createXMLHttpRequest = function() { } throw new js__$Boot_HaxeError("Unable to create XMLHttpRequest object."); }; +var js_youtube_Youtube = function() { }; +js_youtube_Youtube.__name__ = true; +js_youtube_Youtube.init = function(onAPIReady) { + var firstElement = window.document.getElementsByTagName("script")[0]; + var script = window.document.createElement("script"); + script.src = "https://www.youtube.com/player_api"; + firstElement.parentNode.insertBefore(script,firstElement); + window.onYouTubePlayerAPIReady = function() { + js_youtube_Youtube.isLoadedAPI = true; + if(onAPIReady != null) { + onAPIReady(); + } + }; +}; 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; @@ -2079,5 +2214,9 @@ Lang.langs = new haxe_ds_StringMap(); Lang.lang = HxOverrides.substr(window.navigator.language,0,2).toLowerCase(); client_Buttons.personalHistory = []; client_Buttons.personalHistoryId = -1; +client_players_Youtube.matchId = new EReg("v=([A-z0-9_-]+)",""); +client_players_Youtube.matchShort = new EReg("youtu.be/([A-z0-9_-]+)",""); +client_players_Youtube.matchEmbed = new EReg("embed/([A-z0-9_-]+)",""); +js_youtube_Youtube.isLoadedAPI = false; client_Main.main(); })(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx index 9f224a8..a20b22b 100644 --- a/src/client/IPlayer.hx +++ b/src/client/IPlayer.hx @@ -3,6 +3,7 @@ package client; import Types.VideoItem; interface IPlayer { + function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void; function loadVideo(item:VideoItem):Void; function removeVideo():Void; function play():Void; diff --git a/src/client/Main.hx b/src/client/Main.hx index 1804ca1..d3271e9 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -161,7 +161,7 @@ class Main { if (matchName.match(name)) name = matchName.matched(1); else name = Lang.get("rawVideo"); - getRemoteVideoDuration(url, (duration:Float) -> { + player.getRemoteDuration(url, (duration:Float) -> { if (duration == 0) { serverMessage(4, Lang.get("addVideoError")); return; @@ -205,22 +205,6 @@ class Main { return url.replace(globalIp, host); } - function getRemoteVideoDuration(src:String, callback:(duration:Float)->Void):Void { - final player:Element = ge("#ytapiplayer"); - final video = document.createVideoElement(); - video.src = src; - // TODO catch errors on AddVideo and getRemoteVideoDuration - video.onerror = e -> { - if (player.contains(video)) player.removeChild(video); - callback(0); - } - video.onloadedmetadata = () -> { - if (player.contains(video)) player.removeChild(video); - callback(video.duration); - } - Utils.prepend(player, video); - } - function onMessage(e):Void { final data:WsEvent = Json.parse(e.data); final t:String = cast data.type; diff --git a/src/client/Player.hx b/src/client/Player.hx index 3941bdb..7bd2f4e 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -4,9 +4,9 @@ import js.html.Element; import js.Browser.document; import client.Main.ge; import client.players.Raw; +import client.players.Youtube; import Types.VideoItem; using StringTools; -using Lambda; class Player { @@ -19,7 +19,6 @@ class Player { var itemPos = 0; var isLoaded = false; var skipSetTime = false; - final matchYoutube = ~/v=([A-z0-9_-]+)/; public function new(main:Main):Void { this.main = main; @@ -81,17 +80,21 @@ class Player { this.player = player; } - function isYoutube(url:String):Bool { - if (!url.contains("youtube.com/")) return false; - if (!url.contains("youtu.be/")) return false; - if (!matchYoutube.match(url)) return false; - return true; + public function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void { + if (Youtube.isYoutube(url)) { + new Youtube(main, this).getRemoteDuration(url, callback); + } else { + new Raw(main, this).getRemoteDuration(url, callback); + } } public function setVideo(i:Int):Void { final item = items[i]; - if (isYoutube(item.url)) {} // setPlayer(new Youtube(main, this)); - else setPlayer(new Raw(main, this)); + if (Youtube.isYoutube(item.url)) { + setPlayer(new Youtube(main, this)); + } else { + setPlayer(new Raw(main, this)); + } final childs = videoItemsEl.children; if (childs[itemPos] != null) { diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index 41b421c..1e5d245 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -11,14 +11,29 @@ class Raw implements IPlayer { final main:Main; final player:Player; - var video:VideoElement; final playerEl:Element = ge("#ytapiplayer"); + var video:VideoElement; public function new(main:Main, player:Player) { this.main = main; this.player = player; } + public function getRemoteDuration(src:String, callback:(duration:Float)->Void):Void { + final video = document.createVideoElement(); + video.src = src; + // TODO catch errors on AddVideo and getRemoteVideoDuration + video.onerror = e -> { + if (playerEl.contains(video)) playerEl.removeChild(video); + callback(0); + } + video.onloadedmetadata = () -> { + if (playerEl.contains(video)) playerEl.removeChild(video); + callback(video.duration); + } + Utils.prepend(playerEl, video); + } + public function loadVideo(item:VideoItem):Void { video = document.createVideoElement(); video.id = "videoplayer"; diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx new file mode 100644 index 0000000..cba3fc4 --- /dev/null +++ b/src/client/players/Youtube.hx @@ -0,0 +1,145 @@ +package client.players; + +import js.html.Element; +import js.Browser.document; +import client.Main.ge; +import js.youtube.Youtube as YtInit; +import js.youtube.YoutubePlayer; +import Types.VideoItem; +using StringTools; + +class Youtube implements IPlayer { + + static final matchId = ~/v=([A-z0-9_-]+)/; + static final matchShort = ~/youtu.be\/([A-z0-9_-]+)/; + static final matchEmbed = ~/embed\/([A-z0-9_-]+)/; + final main:Main; + final player:Player; + final playerEl:Element = ge("#ytapiplayer"); + var video:Element; + var youtube:YoutubePlayer; + var isLoaded = false; + + public function new(main:Main, player:Player) { + this.main = main; + this.player = player; + } + + public static function isYoutube(url:String):Bool { + if (url.contains("youtu.be/")) { + return matchShort.match(url); + } + if (url.contains("youtube.com/embed/")) { + return matchEmbed.match(url); + } + if (!url.contains("youtube.com/")) return false; + return matchId.match(url); + } + + function extractVideoId(url:String):String { + if (url.contains("youtu.be/")) { + matchShort.match(url); + return matchShort.matched(1); + } + if (url.contains("youtube.com/embed/")) { + matchEmbed.match(url); + return matchEmbed.matched(1); + } + matchId.match(url); + return matchId.matched(1); + } + + public function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void { + if (!YtInit.isLoadedAPI) { + YtInit.init(() -> getRemoteDuration(url, callback)); + return; + } + final video = document.createDivElement(); + video.id = "temp-videoplayer"; + Utils.prepend(playerEl, video); + youtube = new YoutubePlayer(video.id, { + videoId: extractVideoId(url), + playerVars: { + modestbranding: 1, + rel: 0, + showinfo: 0 + }, + events: { + onReady: e -> { + if (playerEl.contains(video)) playerEl.removeChild(video); + callback(youtube.getDuration()); + }, + onError: e -> { + trace('Error ${e.data}'); + if (playerEl.contains(video)) playerEl.removeChild(video); + callback(0); + } + } + }); + } + + public function loadVideo(item:VideoItem):Void { + if (!YtInit.isLoadedAPI) { + YtInit.init(() -> loadVideo(item)); + return; + } + video = document.createDivElement(); + video.id = "videoplayer"; + playerEl.appendChild(video); + + youtube = new YoutubePlayer(video.id, { + videoId: extractVideoId(item.url), + playerVars: { + autoplay: 1, + modestbranding: 1, + rel: 0, + showinfo: 0, + }, + events: { + // onReady: e -> player.onCanBePlayed(), + onStateChange: e -> { + switch (e.data) { + case UNSTARTED: + isLoaded = true; + player.onCanBePlayed(); + case ENDED: + case PLAYING: + player.onPlay(); + case PAUSED: + player.onPause(); + case BUFFERING: + player.onSetTime(); + case CUED: + } + } + } + }); + } + + public function removeVideo():Void { + if (video == null) return; + playerEl.removeChild(video); + video = null; + } + + public function play():Void { + if (!isLoaded) return; + youtube.playVideo(); + } + + public function pause():Void { + if (!isLoaded) return; + youtube.pauseVideo(); + } + + public function getTime():Float { + if (!isLoaded) return 0; + return youtube.getCurrentTime(); + } + + public function setTime(time:Float):Void { + if (!isLoaded) return; + youtube.seekTo((time : Any), true); + } + +} diff --git a/src/server/Main.hx b/src/server/Main.hx index e5b50c8..aaf9277 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -248,11 +248,11 @@ class Main { case AddVideo: final item = data.addVideo.item; + item.author = client.name; final local = '$localIp:$port'; if (item.url.contains(local)) { item.url = item.url.replace(local, '$globalIp:$port'); } - item.author = client.name; if (videoList.exists(i -> i.url == item.url)) { // TODO send server message return; @@ -290,12 +290,14 @@ class Main { case Pause: if (videoList.length == 0) return; if (!client.isLeader) return; + videoTimer.setTime(data.pause.time); videoTimer.pause(); broadcast(data); case Play: if (videoList.length == 0) return; if (!client.isLeader) return; + videoTimer.setTime(data.play.time); videoTimer.play(); broadcast(data); -- cgit v1.2.3