From dd254b65cbfe89faf364c1808599ce0c2ad50c63 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 24 Apr 2018 14:36:05 +0200 Subject: [PATCH] Implemented YouTube videos search and controls web FE --- .../http/static/css/video.omxplayer.css | 63 +++++++++- platypush/backend/http/static/js/music.mpd.js | 19 +-- .../backend/http/static/js/video.omxplayer.js | 119 ++++++++++++++---- .../templates/plugins/video.omxplayer.html | 27 +++- platypush/plugins/video/omxplayer.py | 51 +++++--- 5 files changed, 221 insertions(+), 58 deletions(-) diff --git a/platypush/backend/http/static/css/video.omxplayer.css b/platypush/backend/http/static/css/video.omxplayer.css index 4c8d6efd0b..c0cff1dfba 100644 --- a/platypush/backend/http/static/css/video.omxplayer.css +++ b/platypush/backend/http/static/css/video.omxplayer.css @@ -1,6 +1,6 @@ #video-search { max-width: 60em; - margin: 3em auto 1em auto; + margin: 1em auto; } #video-search input[type=text] { @@ -11,3 +11,64 @@ form#video-ctrl { text-align: center; } +#video-seeker-container { + margin-top: 0.5em; + margin-bottom: 1em; +} + +#video-volume-ctrl-container { + margin-top: 1em; +} + +#video-results { + padding: 1.5rem 1.5rem 0 .5rem; + background: #f8f8f8; +} + + .video-result { + padding: 5px; + letter-spacing: .1rem; + line-height: 3.3rem; + cursor: pointer; + } + + .video-result.selected { + background-color: #c8ffd0 !important; + } + + .video-result:hover { + background-color: #daf8e2 !important; + } + + .video-result:nth-child(odd) { + background-color: #f2f2f2; + } + + .video-result.active { + height: 4rem; + padding-top: 1.5rem; + font-size: 1.7rem; + border-radius: 10px; + animation: active-track 5s; + -moz-animation: active-track 5s infinite; + -webkit-animation: active-track 5s infinite; + } + + @keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-moz-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-webkit-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + diff --git a/platypush/backend/http/static/js/music.mpd.js b/platypush/backend/http/static/js/music.mpd.js index 7613e7169d..70f94d3d7e 100644 --- a/platypush/backend/http/static/js/music.mpd.js +++ b/platypush/backend/http/static/js/music.mpd.js @@ -521,26 +521,11 @@ $(document).ready(function() { ); }); - $volumeCtrl.on('mousedown', function(event) { + $volumeCtrl.on('mousedown touchstart', function(event) { prevVolume = $(this).val(); }); - $volumeCtrl.on('mouseup', function(event) { - execute( - { - type: 'request', - action: 'music.mpd.setvol', - args: { vol: $(this).val() } - }, - - onSuccess=undefined, - onError = function() { - $volumeCtrl.val(prevVolume); - } - ); - }); - - $volumeCtrl.on('mouseup', function(event) { + $volumeCtrl.on('mouseup touchend', function(event) { execute( { type: 'request', diff --git a/platypush/backend/http/static/js/video.omxplayer.js b/platypush/backend/http/static/js/video.omxplayer.js index dd7c9e8c7b..d19465fd77 100644 --- a/platypush/backend/http/static/js/video.omxplayer.js +++ b/platypush/backend/http/static/js/video.omxplayer.js @@ -1,41 +1,66 @@ $(document).ready(function() { var $container = $('#video-container'), $searchForm = $('#video-search'), - $ctrlForm = $('#video-ctrl'); + $videoResults = $('#video-results'), + $volumeCtrl = $('#video-volume-ctrl'), + $ctrlForm = $('#video-ctrl'), + prevVolume = undefined; + + var updateVideoResults = function(videos) { + $videoResults.html(''); + for (var video of videos) { + var $videoResult = $('
') + .addClass('video-result') + .attr('data-url', video['url']) + .html('title' in video ? video['title'] : video['url']); + + $videoResult.appendTo($videoResults); + } + }; var initBindings = function() { $searchForm.on('submit', function(event) { - var formData = $(this).serializeArray().reduce(function(obj, item) { - var value = item.value.trim(); - if (value.length > 0) { - obj[item.name] = item.value; - } + var $input = $(this).find('input[name=video-search-text]'); + var resource = $input.val(); + var request = {} + var onSuccess = function() {}; + var onError = function() {}; + var onComplete = function() { + $input.prop('disabled', false); + }; - return obj; - }, {}); + $input.prop('disabled', true); + $videoResults.text('Searching...'); - execute( - { + if (resource.match(new RegExp('^https?://')) || + resource.match(new RegExp('^file://'))) { + var videos = [{ url: resource }]; + updateVideoResults(videos); + + request = { type: 'request', - action: 'video.omxplayer.stop', - }, + action: 'video.omxplayer.play', + args: { resource: resource } + }; + } else { + request = { + type: 'request', + action: 'video.omxplayer.youtube_search', + args: { query: resource } + }; - function() { - execute( - { - type: 'request', - action: 'video.omxplayer.play', - args: formData, - } - ) - } - ); + onSuccess = function(response) { + var videos = response.response.output; + updateVideoResults(videos); + }; + } + execute(request, onSuccess, onError, onComplete) return false; }); $ctrlForm.on('submit', function() { return false; }); - $ctrlForm.find('button[data-action]').on('click', function(evt) { + $ctrlForm.find('button[data-action]').on('click touch', function(evt) { var action = $(this).data('action'); var $btn = $(this); @@ -46,6 +71,54 @@ $(document).ready(function() { } ); }); + + $volumeCtrl.on('mousedown touchstart', function(event) { + prevVolume = $(this).val(); + }); + + $volumeCtrl.on('mouseup touchend', function(event) { + execute( + { + type: 'request', + action: 'video.omxplayer.set_volume', + args: { volume: $(this).val() } + }, + + onSuccess=undefined, + onError = function() { + $volumeCtrl.val(prevVolume); + } + ); + }); + + $videoResults.on('click touch', '.video-result', function(evt) { + var results = $videoResults.html(); + var $item = $(this); + if (!$item.hasClass('selected')) { + $item.siblings().removeClass('selected'); + $item.addClass('selected'); + return false; + } + + $videoResults.text('Loading video...'); + execute( + { + type: 'request', + action: 'video.omxplayer.play', + args: { resource: $item.data('url') }, + }, + + function() { + $videoResults.html(results); + $item.siblings().removeClass('active'); + $item.addClass('active'); + }, + + function() { + $videoResults.html(results); + }, + ); + }); }; var init = function() { diff --git a/platypush/backend/http/templates/plugins/video.omxplayer.html b/platypush/backend/http/templates/plugins/video.omxplayer.html index 3d4ec3aace..e541972c7c 100644 --- a/platypush/backend/http/templates/plugins/video.omxplayer.html +++ b/platypush/backend/http/templates/plugins/video.omxplayer.html @@ -4,14 +4,14 @@
+ +
+
+   + +   +
+
+ +
+
+
diff --git a/platypush/plugins/video/omxplayer.py b/platypush/plugins/video/omxplayer.py index bec12148ff..96df27f8e3 100644 --- a/platypush/plugins/video/omxplayer.py +++ b/platypush/plugins/video/omxplayer.py @@ -30,6 +30,15 @@ class VideoOmxplayerPlugin(Plugin): logging.info('Playing {}'.format(resource)) + if self.player: + try: + self.player.stop() + self.player = None + except Exception as e: + logging.exception(e) + logging.warning('Unable to stop a previously running instance ' + + 'of OMXPlayer, trying to play anyway') + try: self.player = OMXPlayer(resource, args=self.args) self._init_player_handlers() @@ -145,29 +154,40 @@ class VideoOmxplayerPlugin(Plugin): 'state': PlayerState.STOP.value })) + def on_play(self): + def _f(player): + self.bus.post(VideoPlayEvent(video=self.player.get_source())) + return _f + + def on_pause(self): + def _f(player): + self.bus.post(VideoPauseEvent(video=self.player.get_source())) + return _f + + def on_stop(self): + def _f(player): + self.bus.post(VideoStopEvent()) + return _f + + def _init_player_handlers(self): if not self.player: return - self.player.playEvent += lambda _: \ - self.bus.post(VideoPlayEvent(video=self.player.get_source())) - - self.player.pauseEvent += lambda _: \ - self.bus.post(VideoPauseEvent(video=self.player.get_source())) - - self.player.stopEvent += lambda _: \ - self.bus.post(VideoStopEvent()) + self.player.playEvent += self.on_play() + self.player.pauseEvent += self.on_pause() + self.player.stopEvent += self.on_stop() def youtube_search_and_play(self, query): - self.videos_queue = self.youtube_search(query) + self.videos_queue = self.youtube_search(query).output ret = None while self.videos_queue: - url = self.videos_queue.pop(0) - logging.info('Playing {}'.format(url)) + video = self.videos_queue.pop(0) + logging.info('Playing "{}" from [{}]'.format(video['url'], video['title'])) try: - ret = self.play(url) + ret = self.play(video['url']) break except Exception as e: logging.exception(e) @@ -186,12 +206,15 @@ class VideoOmxplayerPlugin(Plugin): for vid in soup.findAll(attrs={'class':'yt-uix-tile-link'}): m = re.match('(/watch\?v=[^&]+)', vid['href']) if m: - results.append('https://www.youtube.com' + m.group(1)) + results.append({ + 'url': 'https://www.youtube.com' + m.group(1), + 'title': vid['title'], + }) logging.info('{} YouTube video results for the search query "{}"' .format(len(results), query)) - return results + return Response(output=results) @classmethod