From 8f1fa59b7541858dbce4a04e5f26977bec07633b Mon Sep 17 00:00:00 2001 From: Sebastien Date: Thu, 22 Nov 2018 16:57:48 +0200 Subject: [PATCH 1/5] Remove auto focus of search bar, no appropriate for mobile --- src/main/resources/static/js/music.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/static/js/music.js b/src/main/resources/static/js/music.js index b43098c..4cb34ba 100644 --- a/src/main/resources/static/js/music.js +++ b/src/main/resources/static/js/music.js @@ -435,8 +435,6 @@ function search() { list.addClass("scale-in"); enableBtn($('#btn_search')); input_search.removeAttr("disabled"); - input_search.focus(); - }).fail( (data)=>{ if(data.status === 401){ From e44bbdbab7d496f0fa29025e81f6676a0f9ced10 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 28 Nov 2018 12:38:27 +0200 Subject: [PATCH 2/5] Correct clear playlist bug --- src/main/resources/static/js/music.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/resources/static/js/music.js b/src/main/resources/static/js/music.js index 4cb34ba..ecae06a 100644 --- a/src/main/resources/static/js/music.js +++ b/src/main/resources/static/js/music.js @@ -105,6 +105,10 @@ function getCurentMusic() { $('#current_time').text("00:00"); btn_play.removeClass("amber"); btn_play.addClass("green"); + if(savedPlaylist != null){ + $('#playlist_list').empty(); + savedPlaylist = null; + } break; @@ -163,6 +167,10 @@ function getCurentMusic() { } clearInterval(interval); + if(savedPlaylist != null){ + $('#playlist_list').empty(); + savedPlaylist = null; + } break; } if (switchAutoFlow.is(':checked') != data.autoflow) @@ -533,14 +541,14 @@ function listeners() { $('#btn_play').click(function () { switch (state) { case "PLAYING": - sendCommand({command: "PAUSE"}, true); + sendCommand({command: "PAUSE"}, false); break; case "PAUSE": - sendCommand({command: "PLAY"}, true); + sendCommand({command: "PLAY"}, false); break; default: - sendCommand({command: "PLAY"},true); + sendCommand({command: "PLAY"}, false); } }); @@ -553,10 +561,10 @@ function listeners() { }); $('#btn_next').click(function () { - sendCommand({command: "NEXT"},true); - }); + sendCommand({command: "NEXT"}, false); + }) $('#btn_stop').click(function () { - sendCommand({command: "STOP"}, true); + sendCommand({command: "STOP"}, false); }); @@ -582,7 +590,7 @@ function listeners() { var command = { command: "FLUSH" }; - sendCommand(command, true); + sendCommand(command, false); }); $('#btn_ok_channel').click(function () { From b01f8721dae598b3439d6a521d9ddfd0f23d5913 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Thu, 29 Nov 2018 14:08:21 +0200 Subject: [PATCH 3/5] disable button when send command on music page --- src/main/resources/static/js/music.js | 14 +++++++++----- src/main/resources/templates/music.html | 14 ++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/resources/static/js/music.js b/src/main/resources/static/js/music.js index ecae06a..68af8d0 100644 --- a/src/main/resources/static/js/music.js +++ b/src/main/resources/static/js/music.js @@ -245,6 +245,7 @@ function getPlayList() { modal_loading.close(); loadingFlag = false; } + $(".ctl-btn").removeClass("disabled"); }).fail(function (data) { @@ -343,12 +344,13 @@ function updateControl(data) { updateModal(data); } -function sendCommand(command, stopRefresh) { - if(stopRefresh){ - clearInterval(interval); +function sendCommand(command, modal) { + clearInterval(interval); + if(modal){ modal_loading.open(); } + $(".ctl-btn").addClass("disabled"); $.ajax({ type: "POST", @@ -358,8 +360,8 @@ function sendCommand(command, stopRefresh) { data: JSON.stringify(command), success: function (data) { loadingFlag = true; - if(stopRefresh) - interval = setInterval("getCurentMusic()", 1000); + interval = setInterval("getCurentMusic()", 1000); + if(command.command === "ADD"){ M.toast({ html: " check_circle Video added to playlist!", @@ -368,6 +370,7 @@ function sendCommand(command, stopRefresh) { }); } + } }).fail(function (data) { @@ -385,6 +388,7 @@ function sendCommand(command, stopRefresh) { displayLength: 99999999 }); } + $(".ctl-btn").removeClass("disabled"); }); } diff --git a/src/main/resources/templates/music.html b/src/main/resources/templates/music.html index 8cd26c3..aed3984 100644 --- a/src/main/resources/templates/music.html +++ b/src/main/resources/templates/music.html @@ -73,17 +73,17 @@
@@ -115,7 +115,7 @@
Playlist
@@ -127,7 +127,7 @@
AutoFlow
-
+
- -
  • " + - " \"\"" + - " "+item["title"]+"" + - "

    "+item["channelTittle"]+ " ║ "+ item["publishedAt"].substr(0, item["publishedAt"].indexOf('T'))+"
    " + ytTimeToTime(item["duration"]) + + " \"\"" + + " " + item["title"] + "" + + "

    " + item["channelTittle"] + " ║ " + item["publishedAt"].substr(0, item["publishedAt"].indexOf('T')) + "
    " + ytTimeToTime(item["duration"]) + "

    " + - " add_circle_outline" + + " add_circle_outline" + "
  • " + ""; - list.append(html) }); @@ -448,14 +458,14 @@ function search() { enableBtn($('#btn_search')); input_search.removeAttr("disabled"); - }).fail( (data)=>{ - if(data.status === 401){ + }).fail((data) => { + if (data.status === 401) { M.toast({ html: " warning Unauthorized, please re-login.", classes: 'red', displayLength: 99999999 }); - }else{ + } else { M.toast({ html: " warningInternal server error, please contact dev.", classes: 'red', @@ -473,9 +483,9 @@ function search() { } -function addListClick(event){ +function addListClick(event) { let button; - if(event.target.nodeName === "I"){ + if (event.target.nodeName === "I") { button = event.target.parentNode; } else @@ -495,20 +505,20 @@ function ytTimeToTime(duration) { let hours; let minutes; let seconds; - if(duration === "PT0S") + if (duration === "PT0S") return "🔴 LIVE"; - if(duration.includes("H")) - hours = parseInt(duration.match(/\d*H/)[0].replace("H",""), 10); + if (duration.includes("H")) + hours = parseInt(duration.match(/\d*H/)[0].replace("H", ""), 10); else hours = 0; - if(duration.includes("M")) - minutes = parseInt(duration.match(/\d*M/)[0].replace("M",""), 10); + if (duration.includes("M")) + minutes = parseInt(duration.match(/\d*M/)[0].replace("M", ""), 10); else minutes = 0; - if(duration.includes("S")) - seconds = parseInt(duration.match(/\d*S/)[0].replace("S",""), 10); + if (duration.includes("S")) + seconds = parseInt(duration.match(/\d*S/)[0].replace("S", ""), 10); else seconds = 0; @@ -522,8 +532,6 @@ function ytTimeToTime(duration) { } - - function msToTime(duration) { var milliseconds = parseInt((duration % 1000) / 100) , seconds = parseInt((duration / 1000) % 60) @@ -545,21 +553,21 @@ function listeners() { $('#btn_play').click(function () { switch (state) { case "PLAYING": - sendCommand({command: "PAUSE"}, false); + sendCommand({command: "PAUSE"}, true); break; case "PAUSE": - sendCommand({command: "PLAY"}, false); + sendCommand({command: "PLAY"}, true); break; default: - sendCommand({command: "PLAY"}, false); + sendCommand({command: "PLAY"}, true); } }); $('#btn_search').click(search); - $("form").submit(function(e) { + $("form").submit(function (e) { e.preventDefault(); search(); }); @@ -582,8 +590,6 @@ function listeners() { }); - - $('#modalChanels').change(function () { if ($('#btn_ok_channel').hasClass("disabled")) { $('#btn_ok_channel').removeClass("disabled"); From 23d098780b9c896317abde6e89236b63d403d825 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Thu, 29 Nov 2018 18:37:40 +0200 Subject: [PATCH 5/5] Add playlist search + update lavaplayer + materialize --- build.gradle | 2 +- .../java/net/Broken/Commands/YtSearch.java | 2 +- .../Broken/RestApi/MusicWebAPIController.java | 4 +- .../Broken/audio/AudioPlayerSendHandler.java | 2 +- .../Broken/audio/Youtube/SearchResult.java | 5 +- .../Broken/audio/Youtube/YoutubeTools.java | 66 ++++++--- src/main/resources/static/css/materialize.css | 4 +- src/main/resources/static/js/materialize.js | 38 +++-- src/main/resources/static/js/music.js | 17 ++- src/main/resources/templates/music.html | 137 ++++++++++-------- 10 files changed, 173 insertions(+), 104 deletions(-) diff --git a/build.gradle b/build.gradle index 3ed489b..cc7594a 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ dependencies { exclude group:"org.springframework.boot", module: "spring-boot-starter-logging" } compile("org.springframework.boot:spring-boot-starter-log4j2") - compile("com.sedmelluq:lavaplayer:1.2.49") + compile("com.sedmelluq:lavaplayer:1.3.10") compile 'net.dv8tion:JDA:3.6.0_354' compile group: 'org.json', name: 'json', version: '20160810' compile 'org.springframework.security:spring-security-web:5.0.1.RELEASE' diff --git a/src/main/java/net/Broken/Commands/YtSearch.java b/src/main/java/net/Broken/Commands/YtSearch.java index 9c6232c..c009c35 100644 --- a/src/main/java/net/Broken/Commands/YtSearch.java +++ b/src/main/java/net/Broken/Commands/YtSearch.java @@ -31,7 +31,7 @@ public class YtSearch implements Commande { new MessageTimeOut(MainBot.messageTimeOut, message, event.getMessage()).start(); }else { try { - ArrayList result = youtubeT.search(args[0], 5); + ArrayList result = youtubeT.search(args[0], 5, false); for(SearchResult item : result){ event.getChannel().sendMessage(EmbedMessageUtils.searchResult(item)).queue(); } diff --git a/src/main/java/net/Broken/RestApi/MusicWebAPIController.java b/src/main/java/net/Broken/RestApi/MusicWebAPIController.java index 747778a..1a11f43 100644 --- a/src/main/java/net/Broken/RestApi/MusicWebAPIController.java +++ b/src/main/java/net/Broken/RestApi/MusicWebAPIController.java @@ -200,12 +200,12 @@ public class MusicWebAPIController { } @RequestMapping(value = "/search", method = RequestMethod.GET) - public ResponseEntity> search(@CookieValue("token") String token, @RequestParam(value = "query") String query ){ + public ResponseEntity> search(@CookieValue("token") String token, @RequestParam(value = "query") String query, @RequestParam(value = "playlist") boolean playlist ){ if(token != null) { try { UserEntity user = userUtils.getUserWithApiToken(userRepository, token); YoutubeTools youtubeTools = YoutubeTools.getInstance(); - ArrayList result = youtubeTools.search(query, 25); + ArrayList result = youtubeTools.search(query, 25, playlist); return new ResponseEntity<>(result, HttpStatus.OK); }catch (UnknownTokenException e){ diff --git a/src/main/java/net/Broken/audio/AudioPlayerSendHandler.java b/src/main/java/net/Broken/audio/AudioPlayerSendHandler.java index 29221dc..89fe125 100644 --- a/src/main/java/net/Broken/audio/AudioPlayerSendHandler.java +++ b/src/main/java/net/Broken/audio/AudioPlayerSendHandler.java @@ -37,7 +37,7 @@ public class AudioPlayerSendHandler implements AudioSendHandler { lastFrame = audioPlayer.provide(); } - byte[] data = lastFrame != null ? lastFrame.data : null; + byte[] data = lastFrame != null ? lastFrame.getData() : null; lastFrame = null; return data; diff --git a/src/main/java/net/Broken/audio/Youtube/SearchResult.java b/src/main/java/net/Broken/audio/Youtube/SearchResult.java index 1a36538..7aa75aa 100644 --- a/src/main/java/net/Broken/audio/Youtube/SearchResult.java +++ b/src/main/java/net/Broken/audio/Youtube/SearchResult.java @@ -11,7 +11,10 @@ public class SearchResult { public String duration; public SearchResult(com.google.api.services.youtube.model.SearchResult result, String duration){ - id = result.getId().getVideoId(); + if(result.getId().getVideoId() == null) + id = result.getId().getPlaylistId(); + else + id = result.getId().getVideoId(); title = result.getSnippet().getTitle(); description = result.getSnippet().getDescription(); publishedAt = result.getSnippet().getPublishedAt().toString(); diff --git a/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java b/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java index 369365e..469fbf3 100644 --- a/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java +++ b/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java @@ -3,10 +3,8 @@ package net.Broken.audio.Youtube; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.youtube.YouTube; -import com.google.api.services.youtube.model.SearchListResponse; +import com.google.api.services.youtube.model.*; import com.google.api.services.youtube.model.SearchResult; -import com.google.api.services.youtube.model.Video; -import com.google.api.services.youtube.model.VideoListResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -81,10 +79,13 @@ public class YoutubeTools { } - public ArrayList search(String query, long max) throws IOException { + public ArrayList search(String query, long max, boolean playlist) throws IOException { YouTube youTube = getYoutubeService(); YouTube.Search.List searchList = youTube.search().list("snippet"); - searchList.setType("video"); + if(playlist) + searchList.setType("playlist"); + else + searchList.setType("video"); searchList.setSafeSearch("none"); searchList.setMaxResults(max); searchList.setQ(query); @@ -94,30 +95,55 @@ public class YoutubeTools { SearchListResponse response = searchList.execute(); StringBuilder idString = new StringBuilder(); - for(SearchResult item : response.getItems()){ - idString.append(item.getId().getVideoId()).append(","); - } - HashMap videoHashMap = new HashMap<>(); - YouTube.Videos.List video = youTube.videos().list("contentDetails"); - video.setId(idString.toString()); - video.setKey(apiKey); - VideoListResponse videoResponse = video.execute(); - for(Video item : videoResponse.getItems()){ - videoHashMap.put(item.getId(), item); + if(playlist){ + for(SearchResult item : response.getItems()){ + idString.append(item.getId().getPlaylistId()).append(","); + } + HashMap playlistHashMap = new HashMap<>(); + YouTube.Playlists.List list = youTube.playlists().list("contentDetails"); + list.setId(idString.toString()); + list.setKey(apiKey); + PlaylistListResponse playlistResponse = list.execute(); + for( Playlist item : playlistResponse.getItems()){ + playlistHashMap.put(item.getId(), item); + } + ArrayList finalResult = new ArrayList<>(); + for(SearchResult item : response.getItems()){ + logger.trace(item.getSnippet().getTitle()); + finalResult.add(new net.Broken.audio.Youtube.SearchResult(item, playlistHashMap.get(item.getId().getPlaylistId()).getContentDetails().getItemCount().toString()+ " Video(s)")); + + } + return finalResult; } - ArrayList finalResult = new ArrayList<>(); - for(SearchResult item : response.getItems()){ - logger.trace(item.getSnippet().getTitle()); - finalResult.add(new net.Broken.audio.Youtube.SearchResult(item, videoHashMap.get(item.getId().getVideoId()).getContentDetails().getDuration())); + else{ + for(SearchResult item : response.getItems()){ + idString.append(item.getId().getVideoId()).append(","); + } + HashMap videoHashMap = new HashMap<>(); + YouTube.Videos.List video = youTube.videos().list("contentDetails"); + video.setId(idString.toString()); + video.setKey(apiKey); + VideoListResponse videoResponse = video.execute(); + for(Video item : videoResponse.getItems()){ + videoHashMap.put(item.getId(), item); + } + ArrayList finalResult = new ArrayList<>(); + for(SearchResult item : response.getItems()){ + logger.trace(item.getSnippet().getTitle()); + finalResult.add(new net.Broken.audio.Youtube.SearchResult(item, videoHashMap.get(item.getId().getVideoId()).getContentDetails().getDuration())); + } + return finalResult; } - return finalResult; + + + } diff --git a/src/main/resources/static/css/materialize.css b/src/main/resources/static/css/materialize.css index 416777f..bc6c1fe 100644 --- a/src/main/resources/static/css/materialize.css +++ b/src/main/resources/static/css/materialize.css @@ -1,5 +1,5 @@ /*! - * Materialize v1.0.0-rc.2 (http://materializecss.com) + * Materialize v1.0.0 (http://materializecss.com) * Copyright 2014-2017 Materialize * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) */ @@ -6551,7 +6551,7 @@ textarea.materialize-textarea + label:after, .select-wrapper + label:after { transform-origin: 0 0; } -.input-field > input[type]:-webkit-autofill:not(.browser-default) + label, +.input-field > input[type]:-webkit-autofill:not(.browser-default):not([type="search"]) + label, .input-field > input[type=date]:not(.browser-default) + label, .input-field > input[type=time]:not(.browser-default) + label { -webkit-transform: translateY(-14px) scale(0.8); diff --git a/src/main/resources/static/js/materialize.js b/src/main/resources/static/js/materialize.js index 7e218e6..1e18382 100644 --- a/src/main/resources/static/js/materialize.js +++ b/src/main/resources/static/js/materialize.js @@ -1,5 +1,5 @@ /*! - * Materialize v1.0.0-rc.2 (http://materializecss.com) + * Materialize v1.0.0 (http://materializecss.com) * Copyright 2014-2017 Materialize * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) */ @@ -1084,6 +1084,8 @@ if (typeof define === 'function' && define.amd) { exports.default = M; } +M.version = '1.0.0'; + M.keys = { TAB: 9, ENTER: 13, @@ -2507,7 +2509,11 @@ $jscomp.polyfill = function (e, r, p, m) { var $activatableElement = $(focusedElement).find('a, button').first(); // Click a or button tag if exists, otherwise click li tag - !!$activatableElement.length ? $activatableElement[0].click() : focusedElement.click(); + if (!!$activatableElement.length) { + $activatableElement[0].click(); + } else if (!!focusedElement) { + focusedElement.click(); + } // Close dropdown on ESC } else if (e.which === M.keys.ESC && this.isOpen) { @@ -2687,8 +2693,7 @@ $jscomp.polyfill = function (e, r, p, m) { // onOpenEnd callback if (typeof _this11.options.onOpenEnd === 'function') { - var elem = anim.animatables[0].target; - _this11.options.onOpenEnd.call(elem, _this11.el); + _this11.options.onOpenEnd.call(_this11, _this11.el); } } }); @@ -2719,7 +2724,6 @@ $jscomp.polyfill = function (e, r, p, m) { // onCloseEnd callback if (typeof _this12.options.onCloseEnd === 'function') { - var elem = anim.animatables[0].target; _this12.options.onCloseEnd.call(_this12, _this12.el); } } @@ -2849,7 +2853,7 @@ $jscomp.polyfill = function (e, r, p, m) { Dropdown._dropdowns = []; - window.M.Dropdown = Dropdown; + M.Dropdown = Dropdown; if (M.jQueryLoaded) { M.initializeJqueryWrapper(Dropdown, 'dropdown', 'M_Dropdown'); @@ -4420,7 +4424,7 @@ $jscomp.polyfill = function (e, r, p, m) { return Tabs; }(Component); - window.M.Tabs = Tabs; + M.Tabs = Tabs; if (M.jQueryLoaded) { M.initializeJqueryWrapper(Tabs, 'tabs', 'M_Tabs'); @@ -6090,7 +6094,7 @@ $jscomp.polyfill = function (e, r, p, m) { Sidenav._sidenavs = []; - window.M.Sidenav = Sidenav; + M.Sidenav = Sidenav; if (M.jQueryLoaded) { M.initializeJqueryWrapper(Sidenav, 'sidenav', 'M_Sidenav'); @@ -11833,10 +11837,20 @@ $jscomp.polyfill = function (e, r, p, m) { // Add callback for centering selected option when dropdown content is scrollable dropdownOptions.onOpenEnd = function (el) { var selectedOption = $(_this71.dropdownOptions).find('.selected').first(); - if (_this71.dropdown.isScrollable && selectedOption.length) { - var scrollOffset = selectedOption[0].getBoundingClientRect().top - _this71.dropdownOptions.getBoundingClientRect().top; // scroll to selected option - scrollOffset -= _this71.dropdownOptions.clientHeight / 2; // center in dropdown - _this71.dropdownOptions.scrollTop = scrollOffset; + + if (selectedOption.length) { + // Focus selected option in dropdown + M.keyDown = true; + _this71.dropdown.focusedIndex = selectedOption.index(); + _this71.dropdown._focusFocusedItem(); + M.keyDown = false; + + // Handle scrolling to selected option + if (_this71.dropdown.isScrollable) { + var scrollOffset = selectedOption[0].getBoundingClientRect().top - _this71.dropdownOptions.getBoundingClientRect().top; // scroll to selected option + scrollOffset -= _this71.dropdownOptions.clientHeight / 2; // center in dropdown + _this71.dropdownOptions.scrollTop = scrollOffset; + } } }; diff --git a/src/main/resources/static/js/music.js b/src/main/resources/static/js/music.js index e739f42..6e2eb1b 100644 --- a/src/main/resources/static/js/music.js +++ b/src/main/resources/static/js/music.js @@ -41,6 +41,7 @@ $(document).ready(function () { modal_loading = M.Modal.init($('#modal_loading').get(0), {dismissible: false}); modal_loading.open(); + $('#tabs-swipe-demo').tabs(); $('.dropdown-button').dropdown({ @@ -171,7 +172,6 @@ function getCurentMusic() { } } - clearInterval(interval); if (savedPlaylist != null) { $('#playlist_list').empty(); savedPlaylist = null; @@ -431,14 +431,21 @@ function search() { load.removeClass("hide"); load.addClass("scale-in"); - $.get("/api/music/search?query=" + input_search.val(), (data) => { + $.get("/api/music/search?query=" + input_search.val() + "&playlist=" + $("#playlistSearch").is(':checked'), (data) => { list.empty(); + let url; + if($("#playlistSearch").is(':checked')){ + url = "https://www.youtube.com/playlist?list=" + } + else + url = "https://youtube.com/watch?v="; + data.forEach((item) => { let html = "
  • " + " \"\"" + - " " + item["title"] + "" + + " " + item["title"] + "" + "

    " + item["channelTittle"] + " ║ " + item["publishedAt"].substr(0, item["publishedAt"].indexOf('T')) + "
    " + ytTimeToTime(item["duration"]) + "

    " + " add_circle_outline" + @@ -495,7 +502,7 @@ function addListClick(event) { let command = { command: "ADD", url: button.id, - playlistLimit: $('#limit_range').val(), + playlistLimit: "300", onHead: !$('#bottom').is(':checked') }; sendCommand(command, false); @@ -507,6 +514,8 @@ function ytTimeToTime(duration) { let seconds; if (duration === "PT0S") return "🔴 LIVE"; + if(duration.endsWith("Video(s)")) + return duration; if (duration.includes("H")) hours = parseInt(duration.match(/\d*H/)[0].replace("H", ""), 10); else diff --git a/src/main/resources/templates/music.html b/src/main/resources/templates/music.html index aed3984..8d4d2fc 100644 --- a/src/main/resources/templates/music.html +++ b/src/main/resources/templates/music.html @@ -16,22 +16,23 @@