From 2ade04273717807096a07b6eb132b7677917392d Mon Sep 17 00:00:00 2001 From: RblSb Date: Sat, 29 Feb 2020 14:01:12 +0300 Subject: Get youtube video title/duration with api --- res/client.js | 126 ++++++++++++++++++++++++++++-------------- src/Types.hx | 5 ++ src/client/IPlayer.hx | 3 +- src/client/Main.hx | 17 +++--- src/client/Player.hx | 8 ++- src/client/players/Raw.hx | 18 ++++-- src/client/players/Youtube.hx | 71 +++++++++++++++++++----- 7 files changed, 175 insertions(+), 73 deletions(-) diff --git a/res/client.js b/res/client.js index 14e7ca4..7b90a05 100644 --- a/res/client.js +++ b/res/client.js @@ -779,20 +779,16 @@ client_Main.prototype = { if(!StringTools.startsWith(url,"http")) { url = "" + protocol + "//" + url; } - 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.player.getRemoteDuration(url,function(duration) { - if(duration == 0) { + this.player.getVideoData(url,function(data) { + if(data.duration == 0) { var tmp = Lang.get("addVideoError"); _gthis.serverMessage(4,tmp); return; } - _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration, isTemp : isTemp}, atEnd : atEnd}}); + if(data.title == null) { + data.title = Lang.get("rawVideo"); + } + _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : isTemp}, atEnd : atEnd}}); callback(); return; }); @@ -825,7 +821,7 @@ client_Main.prototype = { 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 : 212, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 211, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); @@ -998,8 +994,9 @@ 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 += " " + e.target.title; + form.value += " " + el.title; form.focus(); return; }; @@ -1248,11 +1245,11 @@ client_Player.prototype = { ,setPlayer: function(player) { this.player = player; } - ,getRemoteDuration: function(url,callback) { + ,getVideoData: function(url,callback) { if(client_players_Youtube.isYoutube(url)) { - new client_players_Youtube(this.main,this).getRemoteDuration(url,callback); + new client_players_Youtube(this.main,this).getVideoData(url,callback); } else { - new client_players_Raw(this.main,this).getRemoteDuration(url,callback); + new client_players_Raw(this.main,this).getVideoData(url,callback); } } ,setVideo: function(i) { @@ -1551,22 +1548,29 @@ var client_players_Raw = function(main,player) { }; client_players_Raw.__name__ = true; client_players_Raw.prototype = { - getRemoteDuration: function(src,callback) { + getVideoData: function(url,callback) { var _gthis = this; + var title = HxOverrides.substr(url,url.lastIndexOf("/") + 1,null); + var matchName = new EReg("^(.+)\\.",""); + if(matchName.match(title)) { + title = matchName.matched(1); + } else { + title = Lang.get("rawVideo"); + } var video = window.document.createElement("video"); - video.src = src; + video.src = url; video.onerror = function(e) { if(_gthis.playerEl.contains(video)) { _gthis.playerEl.removeChild(video); } - callback(0); + callback({ duration : 0}); return; }; video.onloadedmetadata = function() { if(_gthis.playerEl.contains(video)) { _gthis.playerEl.removeChild(video); } - callback(video.duration); + callback({ duration : video.duration, title : title}); return; }; client_Utils.prepend(this.playerEl,video); @@ -1630,6 +1634,9 @@ client_players_Raw.prototype = { } }; var client_players_Youtube = function(main,player) { + this.matchSeconds = new EReg("([0-9]+)S",""); + this.matchMinutes = new EReg("([0-9]+)M",""); + this.matchHours = new EReg("([0-9]+)H",""); this.isLoaded = false; this.playerEl = window.document.querySelector("#ytapiplayer"); this.main = main; @@ -1637,35 +1644,70 @@ var client_players_Youtube = function(main,player) { }; client_players_Youtube.__name__ = true; client_players_Youtube.isYoutube = function(url) { + return client_players_Youtube.extractVideoId(url) != ""; +}; +client_players_Youtube.extractVideoId = function(url) { if(url.indexOf("youtu.be/") != -1) { - return client_players_Youtube.matchShort.match(url); + client_players_Youtube.matchShort.match(url); + return client_players_Youtube.matchShort.matched(1); } if(url.indexOf("youtube.com/embed/") != -1) { - return client_players_Youtube.matchEmbed.match(url); + client_players_Youtube.matchEmbed.match(url); + return client_players_Youtube.matchEmbed.matched(1); } - if(url.indexOf("youtube.com/") == -1) { - return false; + if(!client_players_Youtube.matchId.match(url)) { + return ""; } - return client_players_Youtube.matchId.match(url); + return client_players_Youtube.matchId.matched(1); }; 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); + convertTime: function(duration) { + var total = 0; + var hours = this.matchHours.match(duration); + var minutes = this.matchMinutes.match(duration); + var seconds = this.matchSeconds.match(duration); + if(hours) { + total = Std.parseInt(this.matchHours.matched(1)) * 3600; + } + if(minutes) { + total += Std.parseInt(this.matchMinutes.matched(1)) * 60; } - if(url.indexOf("youtube.com/embed/") != -1) { - client_players_Youtube.matchEmbed.match(url); - return client_players_Youtube.matchEmbed.matched(1); + if(seconds) { + total += Std.parseInt(this.matchSeconds.matched(1)); } - client_players_Youtube.matchId.match(url); - return client_players_Youtube.matchId.matched(1); + return total; + } + ,getVideoData: function(url,callback) { + var _gthis = this; + var url1 = "" + client_players_Youtube.apiUrl + client_players_Youtube.urlTitleDuration + "&id=" + client_players_Youtube.extractVideoId(url) + "&key=" + client_players_Youtube.apiKey; + var http = new haxe_http_HttpJs(url1); + http.onData = function(data) { + var json = JSON.parse(data); + if(json.error != null) { + _gthis.getRemoteDataFallback(url1,callback); + return; + } + var item = json.items[0]; + if(item == null) { + callback({ duration : 0}); + return; + } + var title = item.snippet.title; + var tmp = _gthis.convertTime(item.contentDetails.duration); + callback({ duration : tmp, title : title}); + return; + }; + http.onError = function(msg) { + _gthis.getRemoteDataFallback(url1,callback); + return; + }; + http.request(); } - ,getRemoteDuration: function(url,callback) { + ,getRemoteDataFallback: function(url,callback) { var _gthis = this; if(!js_youtube_Youtube.isLoadedAPI) { js_youtube_Youtube.init(function() { - _gthis.getRemoteDuration(url,callback); + _gthis.getRemoteDataFallback(url,callback); return; }); return; @@ -1673,19 +1715,18 @@ client_players_Youtube.prototype = { 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) { + this.youtube = new YT.Player(video.id,{ videoId : client_players_Youtube.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); + callback({ duration : _gthis.youtube.getDuration()}); return; }, onError : function(e1) { - haxe_Log.trace("Error " + e1.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 74, className : "client.players.Youtube", methodName : "getRemoteDuration"}); + haxe_Log.trace("Error " + e1.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 116, className : "client.players.Youtube", methodName : "getRemoteDataFallback"}); if(_gthis.playerEl.contains(video)) { _gthis.playerEl.removeChild(video); } - callback(0); + callback({ duration : 0}); return; }}}); } @@ -1701,7 +1742,7 @@ client_players_Youtube.prototype = { 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) { + this.youtube = new YT.Player(this.video.id,{ videoId : client_players_Youtube.extractVideoId(item.url), playerVars : { autoplay : 1, modestbranding : 1, rel : 0, showinfo : 0}, events : { onStateChange : function(e) { switch(e.data) { case -1: _gthis.isLoaded = true; @@ -2217,6 +2258,9 @@ 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_-]+)",""); +client_players_Youtube.apiUrl = "https://www.googleapis.com/youtube/v3/videos"; +client_players_Youtube.urlTitleDuration = "?part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"; +client_players_Youtube.apiKey = "AIzaSyDTk1OPRI9cDkAK_BKsBcv10DQCHse-QaA"; 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/Types.hx b/src/Types.hx index 17b010b..8eda528 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -2,6 +2,11 @@ package; import Client.ClientData; +typedef VideoData = { + duration:Float, + ?title:String +} + typedef Config = { channelName:String, maxLoginLength:Int, diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx index a20b22b..4f29512 100644 --- a/src/client/IPlayer.hx +++ b/src/client/IPlayer.hx @@ -1,9 +1,10 @@ package client; +import Types.VideoData; import Types.VideoItem; interface IPlayer { - function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void; + function getVideoData(url:String, callback:(data:VideoData)->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 d3271e9..c74a600 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -12,7 +12,9 @@ import js.Browser; import js.Browser.document; import js.lib.Date; import Client.ClientData; -import Types; +import Types.VideoData; +import Types.Config; +import Types.WsEvent; using StringTools; using ClientTools; @@ -156,23 +158,20 @@ class Main { 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); - else name = Lang.get("rawVideo"); - player.getRemoteDuration(url, (duration:Float) -> { - if (duration == 0) { + player.getVideoData(url, (data:VideoData) -> { + if (data.duration == 0) { serverMessage(4, Lang.get("addVideoError")); return; } + if (data.title == null) data.title = Lang.get("rawVideo"); send({ type: AddVideo, addVideo: { item: { url: url, - title: name, + title: data.title, author: personal.name, - duration: duration, + duration: data.duration, isTemp: isTemp }, atEnd: atEnd diff --git a/src/client/Player.hx b/src/client/Player.hx index 7bd2f4e..4327e6c 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -5,6 +5,7 @@ import js.Browser.document; import client.Main.ge; import client.players.Raw; import client.players.Youtube; +import Types.VideoData; import Types.VideoItem; using StringTools; @@ -80,11 +81,12 @@ class Player { this.player = player; } - public function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void { + public function getVideoData(url:String, callback:(data:VideoData)->Void):Void { + // TODO P2 reuse player objects if (Youtube.isYoutube(url)) { - new Youtube(main, this).getRemoteDuration(url, callback); + new Youtube(main, this).getVideoData(url, callback); } else { - new Raw(main, this).getRemoteDuration(url, callback); + new Raw(main, this).getVideoData(url, callback); } } diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index 1e5d245..44a33f2 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -5,6 +5,7 @@ import js.html.Element; import js.html.VideoElement; import js.Browser.document; import client.Main.ge; +import Types.VideoData; import Types.VideoItem; class Raw implements IPlayer { @@ -19,17 +20,24 @@ class Raw implements IPlayer { this.player = player; } - public function getRemoteDuration(src:String, callback:(duration:Float)->Void):Void { + public function getVideoData(url:String, callback:(data:VideoData)->Void):Void { + var title = url.substr(url.lastIndexOf('/') + 1); + final matchName = ~/^(.+)\./; + if (matchName.match(title)) title = matchName.matched(1); + else title = Lang.get("rawVideo"); + final video = document.createVideoElement(); - video.src = src; - // TODO catch errors on AddVideo and getRemoteVideoDuration + video.src = url; video.onerror = e -> { if (playerEl.contains(video)) playerEl.removeChild(video); - callback(0); + callback({duration: 0}); } video.onloadedmetadata = () -> { if (playerEl.contains(video)) playerEl.removeChild(video); - callback(video.duration); + callback({ + duration: video.duration, + title: title + }); } Utils.prepend(playerEl, video); } diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index cba3fc4..3e84950 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -1,10 +1,13 @@ package client.players; +import haxe.Json; +import haxe.Http; import js.html.Element; import js.Browser.document; import client.Main.ge; import js.youtube.Youtube as YtInit; import js.youtube.YoutubePlayer; +import Types.VideoData; import Types.VideoItem; using StringTools; @@ -13,6 +16,9 @@ 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_-]+)/; + static final apiUrl = "https://www.googleapis.com/youtube/v3/videos"; + static final urlTitleDuration = "?part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"; + static final apiKey = "AIzaSyDTk1OPRI9cDkAK_BKsBcv10DQCHse-QaA"; final main:Main; final player:Player; final playerEl:Element = ge("#ytapiplayer"); @@ -26,17 +32,10 @@ class Youtube implements IPlayer { } 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); + return extractVideoId(url) != ""; } - function extractVideoId(url:String):String { + static function extractVideoId(url:String):String { if (url.contains("youtu.be/")) { matchShort.match(url); return matchShort.matched(1); @@ -45,13 +44,54 @@ class Youtube implements IPlayer { matchEmbed.match(url); return matchEmbed.matched(1); } - matchId.match(url); + if (!matchId.match(url)) return ""; return matchId.matched(1); } - public function getRemoteDuration(url:String, callback:(duration:Float)->Void):Void { + final matchHours = ~/([0-9]+)H/; + final matchMinutes = ~/([0-9]+)M/; + final matchSeconds = ~/([0-9]+)S/; + + function convertTime(duration:String):Float { + var total = 0; + final hours = matchHours.match(duration); + final minutes = matchMinutes.match(duration); + final seconds = matchSeconds.match(duration); + if (hours) total += Std.parseInt(matchHours.matched(1)) * 3600; + if (minutes) total += Std.parseInt(matchMinutes.matched(1)) * 60; + if (seconds) total += Std.parseInt(matchSeconds.matched(1)); + return total; + } + + public function getVideoData(url:String, callback:(data:VideoData)->Void):Void { + final id = extractVideoId(url); + final url = '$apiUrl$urlTitleDuration&id=$id&key=$apiKey'; + final http = new Http(url); + http.onData = data -> { + final json = Json.parse(data); + if (json.error != null) { + getRemoteDataFallback(url, callback); + return; + } + final item = json.items[0]; + if (item == null) { + callback({duration: 0}); + return; + } + final title:String = item.snippet.title; + final duration:String = item.contentDetails.duration; + callback({ + duration: convertTime(duration), + title: title + }); + } + http.onError = msg -> getRemoteDataFallback(url, callback); + http.request(); + } + + function getRemoteDataFallback(url:String, callback:(data:VideoData)->Void):Void { if (!YtInit.isLoadedAPI) { - YtInit.init(() -> getRemoteDuration(url, callback)); + YtInit.init(() -> getRemoteDataFallback(url, callback)); return; } final video = document.createDivElement(); @@ -67,12 +107,15 @@ class Youtube implements IPlayer { events: { onReady: e -> { if (playerEl.contains(video)) playerEl.removeChild(video); - callback(youtube.getDuration()); + callback({ + duration: youtube.getDuration() + }); }, onError: e -> { + // TODO message error codes trace('Error ${e.data}'); if (playerEl.contains(video)) playerEl.removeChild(video); - callback(0); + callback({duration: 0}); } } }); -- cgit v1.2.3