diff options
| author | RblSb <msrblsb@gmail.com> | 2020-03-08 03:20:52 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2020-03-08 03:20:52 +0300 |
| commit | 4df4f2d023d7c2413fb5d6bf9597ce409c3354be (patch) | |
| tree | 9385e57e234e3c27ba1ce3433c63df38a92c3702 | |
| parent | c6709732e858d111a75d14f8b9a132c58487f8d8 (diff) | |
Youtube playlists and api key in config
| -rw-r--r-- | default-config.json | 1 | ||||
| -rw-r--r-- | res/client.js | 108 | ||||
| -rw-r--r-- | res/index.html | 2 | ||||
| -rw-r--r-- | src/Types.hx | 4 | ||||
| -rw-r--r-- | src/client/Main.hx | 12 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 83 |
6 files changed, 173 insertions, 37 deletions
diff --git a/default-config.json b/default-config.json index 37c3861..6d0a99e 100644 --- a/default-config.json +++ b/default-config.json @@ -7,6 +7,7 @@ "totalVideoLimit": 0, "userVideoLimit": 0, "templateUrl": "https://youtube.com/watch?v=iY1QHpp6iEE", + "youtubeApiKey": "AIzaSyDTk1OPRI9cDkAK_BKsBcv10DQCHse-QaA", "permissions": { "guest": ["writeChat", "addVideo", "removeVideo", "changeOrder", "requestLeader", "rewind"], "user": ["guest"], diff --git a/res/client.js b/res/client.js index 721babb..240d382 100644 --- a/res/client.js +++ b/res/client.js @@ -921,7 +921,10 @@ client_Main.prototype = { 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, isIframe : false}, atEnd : atEnd}}); + if(data.url == null) { + data.url = url; + } + _gthis.send({ type : "AddVideo", addVideo : { item : { url : data.url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : isTemp, isIframe : false}, atEnd : atEnd}}); callback(); return; }); @@ -970,7 +973,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 : 257, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 258, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); @@ -1237,7 +1240,10 @@ client_Main.prototype = { } this.ws.send(JSON.stringify(data)); } - ,serverMessage: function(type,text) { + ,serverMessage: function(type,text,isText) { + if(isText == null) { + isText = true; + } var msgBuf = window.document.querySelector("#messagebuffer"); var div = window.document.createElement("div"); var time = "[" + new Date().toTimeString().split(" ")[0] + "] "; @@ -1256,7 +1262,11 @@ client_Main.prototype = { break; case 4: div.className = "server-whisper"; - div.textContent = time + text; + if(isText) { + div.textContent = time + text; + } else { + div.innerHTML = time + text; + } break; default: } @@ -1384,6 +1394,9 @@ client_Main.prototype = { ,getTemplateUrl: function() { return this.config.templateUrl; } + ,getYoutubeApiKey: function() { + return this.config.youtubeApiKey; + } ,escapeRegExp: function(regex) { var _this_r = new RegExp("([.*+?^${}()|[\\]\\\\])","g".split("u").join("")); return regex.replace(_this_r,"\\$1"); @@ -1907,10 +1920,15 @@ var client_players_Youtube = function(main,player) { this.playerEl = window.document.querySelector("#ytapiplayer"); this.main = main; this.player = player; + client_players_Youtube.apiKey = main.getYoutubeApiKey(); }; client_players_Youtube.__name__ = true; client_players_Youtube.isYoutube = function(url) { - return client_players_Youtube.extractVideoId(url) != ""; + if(client_players_Youtube.extractVideoId(url) == "") { + return client_players_Youtube.extractPlaylistId(url) != ""; + } else { + return true; + } }; client_players_Youtube.extractVideoId = function(url) { if(url.indexOf("youtu.be/") != -1) { @@ -1926,6 +1944,12 @@ client_players_Youtube.extractVideoId = function(url) { } return client_players_Youtube.matchId.matched(1); }; +client_players_Youtube.extractPlaylistId = function(url) { + if(!client_players_Youtube.matchPlaylist.match(url)) { + return ""; + } + return client_players_Youtube.matchPlaylist.matched(1); +}; client_players_Youtube.prototype = { convertTime: function(duration) { var total = 0; @@ -1945,30 +1969,78 @@ client_players_Youtube.prototype = { } ,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); + var id = client_players_Youtube.extractVideoId(url); + if(id == "") { + this.getPlaylistVideoData(url,callback); + return; + } + var http = new haxe_http_HttpJs("" + client_players_Youtube.videosUrl + client_players_Youtube.urlTitleDuration + "&id=" + id + "&key=" + client_players_Youtube.apiKey); http.onData = function(data) { var json = JSON.parse(data); if(json.error != null) { - _gthis.getRemoteDataFallback(url1,callback); + _gthis.youtubeApiError(json.error); + _gthis.getRemoteDataFallback(url,callback); return; } - var item = json.items[0]; - if(item == null) { + var items = json.items; + if(items == null || items.length == 0) { callback({ duration : 0}); return; } - var title = item.snippet.title; - var tmp = _gthis.convertTime(item.contentDetails.duration); - callback({ duration : tmp, title : title}); + var _g = 0; + while(_g < items.length) { + var item = items[_g]; + ++_g; + var title = item.snippet.title; + var tmp = _gthis.convertTime(item.contentDetails.duration); + callback({ duration : tmp, title : title, url : url}); + } return; }; http.onError = function(msg) { - _gthis.getRemoteDataFallback(url1,callback); + _gthis.getRemoteDataFallback(url,callback); return; }; http.request(); } + ,getPlaylistVideoData: function(url,callback) { + var _gthis = this; + var http = new haxe_http_HttpJs("" + client_players_Youtube.playlistUrl + client_players_Youtube.urlVideoId + "&maxResults=50&playlistId=" + client_players_Youtube.extractPlaylistId(url) + "&key=" + client_players_Youtube.apiKey); + http.onData = function(data) { + var json = JSON.parse(data); + if(json.error != null) { + _gthis.youtubeApiError(json.error); + callback({ duration : 0}); + return; + } + var items = json.items; + if(items == null || items.length == 0) { + callback({ duration : 0}); + return; + } + var loadNextItem = null; + loadNextItem = function() { + var loadNextItem1 = "youtu.be/" + items.shift().snippet.resourceId.videoId; + _gthis.getVideoData(loadNextItem1,function(data1) { + callback(data1); + if(items.length > 0) { + loadNextItem(); + } + return; + }); + }; + loadNextItem(); + return; + }; + http.onError = function(msg) { + callback({ duration : 0}); + return; + }; + http.request(); + } + ,youtubeApiError: function(error) { + this.main.serverMessage(4,"Error " + error.code + ": " + error.message,false); + } ,getRemoteDataFallback: function(url,callback) { var _gthis = this; if(!js_youtube_Youtube.isLoadedAPI) { @@ -1989,7 +2061,7 @@ client_players_Youtube.prototype = { callback({ duration : tmp}); return; }, onError : function(e1) { - haxe_Log.trace("Error " + e1.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 117, className : "client.players.Youtube", methodName : "getRemoteDataFallback"}); + haxe_Log.trace("Error " + e1.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 170, className : "client.players.Youtube", methodName : "getRemoteDataFallback"}); if(_gthis.playerEl.contains(video)) { _gthis.playerEl.removeChild(video); } @@ -2700,9 +2772,11 @@ 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.matchPlaylist = new EReg("youtube\\.com.*list=([A-z0-9_-]+)",""); +client_players_Youtube.videosUrl = "https://www.googleapis.com/youtube/v3/videos"; +client_players_Youtube.playlistUrl = "https://www.googleapis.com/youtube/v3/playlistItems"; client_players_Youtube.urlTitleDuration = "?part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"; -client_players_Youtube.apiKey = "AIzaSyDTk1OPRI9cDkAK_BKsBcv10DQCHse-QaA"; +client_players_Youtube.urlVideoId = "?part=snippet&fields=items(snippet/resourceId/videoId)"; js_youtube_Youtube.isLoadedAPI = false; client_Main.main(); })(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/res/index.html b/res/index.html index 8ad141b..254e4bc 100644 --- a/res/index.html +++ b/res/index.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html lang="ru"><head> +<html><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <style class="vjs-styles-defaults"> .video-js { diff --git a/src/Types.hx b/src/Types.hx index a98c7a1..2501cc4 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -4,7 +4,8 @@ import Client.ClientData; typedef VideoData = { duration:Float, - ?title:String + ?title:String, + ?url:String } typedef Config = { @@ -16,6 +17,7 @@ typedef Config = { totalVideoLimit:Int, userVideoLimit:Int, templateUrl:String, + youtubeApiKey:String, permissions:{ guest:Array<Permission>, user:Array<Permission>, diff --git a/src/client/Main.hx b/src/client/Main.hx index 1552d38..918a81a 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -182,10 +182,11 @@ class Main { return; } if (data.title == null) data.title = Lang.get("rawVideo"); + if (data.url == null) data.url = url; send({ type: AddVideo, addVideo: { item: { - url: url, + url: data.url, title: data.title, author: personal.name, duration: data.duration, @@ -502,7 +503,7 @@ class Main { ws.send(Json.stringify(data)); } - function serverMessage(type:Int, ?text:String):Void { + public function serverMessage(type:Int, ?text:String, isText = true):Void { final msgBuf = ge("#messagebuffer"); final div = document.createDivElement(); final time = "[" + new Date().toTimeString().split(" ")[0] + "] "; @@ -518,7 +519,8 @@ class Main { div.textContent = time + text + " " + Lang.get("entered"); case 4: div.className = "server-whisper"; - div.textContent = time + text; + if (isText) div.textContent = time + text; + else div.innerHTML = time + text; default: } msgBuf.appendChild(div); @@ -650,6 +652,10 @@ class Main { return config.templateUrl; } + public function getYoutubeApiKey():String { + return config.youtubeApiKey; + } + function escapeRegExp(regex:String):String { return ~/([.*+?^${}()|[\]\\])/g.replace(regex, "\\$1"); } diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index 15eb014..f582ba7 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -16,9 +16,12 @@ 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 matchPlaylist = ~/youtube\.com.*list=([A-z0-9_-]+)/; + static final videosUrl = "https://www.googleapis.com/youtube/v3/videos"; + static final playlistUrl = "https://www.googleapis.com/youtube/v3/playlistItems"; static final urlTitleDuration = "?part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"; - static final apiKey = "AIzaSyDTk1OPRI9cDkAK_BKsBcv10DQCHse-QaA"; + static final urlVideoId = "?part=snippet&fields=items(snippet/resourceId/videoId)"; + static var apiKey:String; final main:Main; final player:Player; final playerEl:Element = ge("#ytapiplayer"); @@ -29,10 +32,11 @@ class Youtube implements IPlayer { public function new(main:Main, player:Player) { this.main = main; this.player = player; + apiKey = main.getYoutubeApiKey(); } public static function isYoutube(url:String):Bool { - return extractVideoId(url) != ""; + return extractVideoId(url) != "" || extractPlaylistId(url) != ""; } static function extractVideoId(url:String):String { @@ -48,6 +52,11 @@ class Youtube implements IPlayer { return matchId.matched(1); } + static function extractPlaylistId(url:String):String { + if (!matchPlaylist.match(url)) return ""; + return matchPlaylist.matched(1); + } + final matchHours = ~/([0-9]+)H/; final matchMinutes = ~/([0-9]+)M/; final matchSeconds = ~/([0-9]+)S/; @@ -64,32 +73,76 @@ class Youtube implements IPlayer { } 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); + var id = extractVideoId(url); + if (id == "") { + getPlaylistVideoData(url, callback); + return; + } + final dataUrl = '$videosUrl$urlTitleDuration&id=$id&key=$apiKey'; + final http = new Http(dataUrl); http.onData = data -> { final json = Json.parse(data); if (json.error != null) { + youtubeApiError(json.error); getRemoteDataFallback(url, callback); return; } - final item = json.items[0]; - if (item == null) { + final items:Array<Dynamic> = json.items; + if (items == null || items.length == 0) { callback({duration: 0}); return; } - final title:String = item.snippet.title; - final duration:String = item.contentDetails.duration; - // TODO duration is PT0S for streams - callback({ - duration: convertTime(duration), - title: title - }); + for (item in items) { + final title:String = item.snippet.title; + final duration:String = item.contentDetails.duration; + // TODO duration is PT0S for streams + callback({ + duration: convertTime(duration), + title: title, + url: url + }); + } } http.onError = msg -> getRemoteDataFallback(url, callback); http.request(); } + function getPlaylistVideoData(url:String, callback:(data:VideoData)->Void):Void { + final id = extractPlaylistId(url); + final dataUrl = '$playlistUrl$urlVideoId&maxResults=50&playlistId=$id&key=$apiKey'; + final http = new Http(dataUrl); + http.onData = data -> { + final json = Json.parse(data); + if (json.error != null) { + youtubeApiError(json.error); + callback({duration: 0}); + return; + } + final items:Array<Dynamic> = json.items; + if (items == null || items.length == 0) { + callback({duration: 0}); + return; + } + function loadNextItem():Void { + final item = items.shift(); + final id:String = item.snippet.resourceId.videoId; + getVideoData('youtu.be/$id', data -> { + callback(data); + if (items.length > 0) loadNextItem(); + }); + } + loadNextItem(); + } + http.onError = msg -> callback({duration: 0}); + http.request(); + } + + function youtubeApiError(error:Dynamic):Void { + final code:Int = error.code; + final msg:String = error.message; + main.serverMessage(4, 'Error $code: $msg', false); + } + function getRemoteDataFallback(url:String, callback:(data:VideoData)->Void):Void { if (!YtInit.isLoadedAPI) { YtInit.init(() -> getRemoteDataFallback(url, callback)); |
