Implemented YouTube videos search and controls web FE

This commit is contained in:
Fabio Manganiello 2018-04-24 14:36:05 +02:00
parent 4d45284131
commit dd254b65cb
5 changed files with 221 additions and 58 deletions

View file

@ -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; }
}

View file

@ -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',

View file

@ -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 = $('<div></div>')
.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() {

View file

@ -4,14 +4,14 @@
<div class="row" id="video-container">
<form action="#" id="video-search">
<div class="row">
<label for="resource">
Supported formats: <tt>file://[path]</tt>, <tt>https://www.youtube.com/?v=[video_id]</tt>
<label for="video-search-text">
Supported formats: <tt>file://[path]</tt>, <tt>https://www.youtube.com/?v=[video_id]</tt>, or free text search
</label>
</div>
<div class="row">
<div class="eleven columns">
<input type="text" name="resource" placeholder="Video URL">
<input type="text" name="video-search-text" placeholder="Search query or video URL">
</div>
<div class="one column">
<button type="submit">
@ -22,6 +22,14 @@
</form>
<form action="#" id="video-ctrl">
<!-- <div class="row"> -->
<!-- <div class="eight columns offset-by-two slider-container" id="video-seeker-container"> -->
<!-- <span class="seek-time" id="video-elapsed">-:--</span>&nbsp; -->
<!-- <input type="range" min="0" id="video-seeker" disabled="disabled" class="slider" style="width:75%"> -->
<!-- &nbsp;<span class="seek-time" id="video-length">-:--</span> -->
<!-- </div> -->
<!-- </div> -->
<div class="row">
<div class="ten columns offset-by-one">
<button data-action="previous">
@ -52,6 +60,19 @@
<i class="fa fa-step-forward"></i>
</button>
</div>
</div>
<div class="row">
<div class="eight columns offset-by-two slider-container" id="video-volume-ctrl-container">
<i class="fa fa-volume-down"></i> &nbsp;
<input type="range" min="0" max="100" value="100" id="video-volume-ctrl" class="slider" style="width:80%">
&nbsp; <i class="fa fa-volume-up"></i>
</div>
</div>
</form>
<div class="row" id="video-results-container">
<div id="video-results"></div>
</div>
</div>

View file

@ -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