From 482f6f0765fd0669badd44ee302ed1f5c03aa343 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 22 Jun 2019 00:15:32 +0200 Subject: [PATCH] New media webplugin WIP --- .../static/css/source/common/animations.scss | 10 ++ .../http/static/css/source/common/vars.scss | 2 + .../webpanel/plugins/media/controls.scss | 9 ++ .../webpanel/plugins/media/results.scss | 13 ++- .../webpanel/plugins/music.mpd/index.scss | 22 +--- .../webpanel/plugins/music.mpd/vars.scss | 3 - .../http/static/js/plugins/media/controls.js | 4 +- .../static/js/plugins/media/handlers/file.js | 9 ++ .../http/static/js/plugins/media/index.js | 107 ++++++++++++++---- .../static/js/plugins/media/players/kodi.js | 80 +++++++++++++ .../static/js/plugins/media/players/local.js | 2 +- .../http/static/js/plugins/media/results.js | 5 + .../http/static/js/plugins/music.mpd/utils.js | 2 +- .../templates/plugins/media/controls.html | 24 ++-- .../http/templates/plugins/media/index.html | 4 +- .../http/templates/plugins/media/results.html | 4 +- platypush/message/event/media.py | 36 +++--- platypush/plugins/media/mpv.py | 65 ++++------- 18 files changed, 274 insertions(+), 127 deletions(-) create mode 100644 platypush/backend/http/static/js/plugins/media/players/kodi.js diff --git a/platypush/backend/http/static/css/source/common/animations.scss b/platypush/backend/http/static/css/source/common/animations.scss index 0bc5add04..98f0768a2 100644 --- a/platypush/backend/http/static/css/source/common/animations.scss +++ b/platypush/backend/http/static/css/source/common/animations.scss @@ -34,3 +34,13 @@ } } +.active-glow { + @include animation(active-glow 5s infinite); +} + +@keyframes active-glow { + 0% { background: $active-glow-bg-1; } + 50% { background: $active-glow-bg-2; } + 100% { background: $active-glow-bg-1; } +} + diff --git a/platypush/backend/http/static/css/source/common/vars.scss b/platypush/backend/http/static/css/source/common/vars.scss index 31e917525..0b4fa0421 100644 --- a/platypush/backend/http/static/css/source/common/vars.scss +++ b/platypush/backend/http/static/css/source/common/vars.scss @@ -38,6 +38,8 @@ $fade-in-transition-duration: $fade-transition-duration !default; $fade-out-transition-duration: $fade-transition-duration !default; $roll-in-transition-duration: $roll-transition-duration !default; $roll-out-transition-duration: $roll-transition-duration !default; +$active-glow-bg-1: #d4ffe3 !default; +$active-glow-bg-2: #9cdfb0 !default; //// Notifications $notification-bg: rgba(185, 255, 193, 0.9) !default; diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/media/controls.scss b/platypush/backend/http/static/css/source/webpanel/plugins/media/controls.scss index a45859f39..e8495f020 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/media/controls.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/media/controls.scss @@ -10,6 +10,11 @@ @extend .vertical-center; padding-left: 1rem; line-height: 2.6rem; + + .item-info { + font-size: 1.15em; + letter-spacing: .02em; + } } button { @@ -26,6 +31,10 @@ justify-content: center; } + .position { + margin-top: .75em; + } + button { padding: 0 1.5rem; diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/media/results.scss b/platypush/backend/http/static/css/source/webpanel/plugins/media/results.scss index d72d2edf0..9822b5fd0 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/media/results.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/media/results.scss @@ -1,8 +1,19 @@ +@import 'common/animations'; + .media-plugin { .results { - @include calc(height, '100% - 16rem'); position: relative; // For the dropdown menu overflow: auto; + @include calc(height, '100%'); + + &.resize { + @include calc(height, '100% - 16rem'); + } + + .active { + @extend .active-glow; + height: 4rem; + } .empty { height: 100%; diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss index bf46752e0..37f7264ed 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss @@ -1,6 +1,8 @@ @import 'common/vars'; @import 'common/mixins'; @import 'common/layout'; +@import 'common/animations'; + @import 'webpanel/plugins/music.mpd/vars'; .music-mpd-container { @@ -155,7 +157,7 @@ &.active { height: 4rem; - @include animation(active-track 5s infinite); + @extend .active-glow; } &.move:hover { @@ -449,21 +451,3 @@ } } -@keyframes active-track { - 0% { background: $active-track-bg-1; } - 50% { background: $active-track-bg-2; } - 100% { background: $active-track-bg-1; } -} - -@-moz-keyframes active-track { - 0% { background: $active-track-bg-1; } - 50% { background: $active-track-bg-2; } - 100% { background: $active-track-bg-1; } -} - -@-webkit-keyframes active-track { - 0% { background: $active-track-bg-1; } - 50% { background: $active-track-bg-2; } - 100% { background: $active-track-bg-1; } -} - diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss index 8cfcee555..4bd2c32d8 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss @@ -16,9 +16,6 @@ $empty-playlist-shadow: 2px 1px rgb(235,235,235); $playlist-controls-bg: rgba(247,247,247,0.95); $playlist-controls-border: $default-border-2; -$active-track-bg-1: #d4ffe3; -$active-track-bg-2: #9cdfb0; - $move-mode-track-border: 3px dotted rgb(216,156,136); $move-mode-track-bg: rgba(216,156,136,0.3); diff --git a/platypush/backend/http/static/js/plugins/media/controls.js b/platypush/backend/http/static/js/plugins/media/controls.js index ee9c2d498..81670b9a0 100644 --- a/platypush/backend/http/static/js/plugins/media/controls.js +++ b/platypush/backend/http/static/js/plugins/media/controls.js @@ -1,5 +1,6 @@ Vue.component('media-controls', { template: '#tmpl-media-controls', + mixins: [mediaUtils], props: { bus: { type: Object }, status: { @@ -7,8 +8,5 @@ Vue.component('media-controls', { default: () => {}, }, }, - - methods: { - }, }); diff --git a/platypush/backend/http/static/js/plugins/media/handlers/file.js b/platypush/backend/http/static/js/plugins/media/handlers/file.js index 5dc78b156..0275ddb41 100644 --- a/platypush/backend/http/static/js/plugins/media/handlers/file.js +++ b/platypush/backend/http/static/js/plugins/media/handlers/file.js @@ -16,6 +16,12 @@ MediaHandlers.file = Vue.extend({ action: this.play, }, + { + text: 'Play with subtitles', + iconClass: 'fas fa-closed-captioning', + action: this.searchSubtiles, + }, + { text: 'Download', icon: 'download', @@ -41,6 +47,9 @@ MediaHandlers.file = Vue.extend({ info: function(item) { }, + + searchSubtitles: function(item) { + }, }, }); diff --git a/platypush/backend/http/static/js/plugins/media/index.js b/platypush/backend/http/static/js/plugins/media/index.js index 2e9a107b8..f9ae99e6c 100644 --- a/platypush/backend/http/static/js/plugins/media/index.js +++ b/platypush/backend/http/static/js/plugins/media/index.js @@ -1,9 +1,37 @@ // Will be filled by dynamically loading media type handler scripts const MediaHandlers = {}; +const mediaUtils = { + methods: { + convertTime: function(time) { + time = parseFloat(time); // Normalize strings + var t = {}; + t.h = '' + parseInt(time/3600); + t.m = '' + parseInt(time/60 - t.h*60); + t.s = '' + parseInt(time - (t.h*3600 + t.m*60)); + + for (var attr of ['m','s']) { + if (parseInt(t[attr]) < 10) { + t[attr] = '0' + t[attr]; + } + } + + var ret = []; + if (parseInt(t.h)) { + ret.push(t.h); + } + + ret.push(t.m, t.s); + return ret.join(':'); + }, + }, +}; + Vue.component('media', { template: '#tmpl-media', props: ['config','player'], + mixins: [mediaUtils], + data: function() { return { bus: new Vue({}), @@ -54,6 +82,38 @@ Vue.component('media', { }); }, + pause: async function() { + let status = await this.selectedDevice.pause(); + this.onStatusUpdate({ + device: this.selectedDevice, + status: status, + }); + }, + + stop: async function() { + let status = await this.selectedDevice.stop(); + this.onStatusUpdate({ + device: this.selectedDevice, + status: status, + }); + }, + + seek: async function(position) { + let status = await this.selectedDevice.seek(position); + this.onStatusUpdate({ + device: this.selectedDevice, + status: status, + }); + }, + + setVolume: async function(volume) { + let status = await this.selectedDevice.setVolume(volume); + this.onStatusUpdate({ + device: this.selectedDevice, + status: status, + }); + }, + info: function(item) { // TODO console.log(item); @@ -78,29 +138,21 @@ Vue.component('media', { Vue.set(this.status[dev.type], dev.name, status); }, - onNewPlayingMedia: function(event) { - console.log('NEW MEDIA'); - console.log(event); - }, + onMediaEvent: async function(event) { + let status = await request(event.plugin + '.status'); - onMediaPlay: function(event) { - console.log('PLAY'); - console.log(event); - }, + if (event.resource) { + event.url = event.resource; + delete event.resource; + } - onMediaPause: function(event) { - console.log('PAUSE'); - console.log(event); - }, + if (event.plugin.startsWith('media.')) + event.plugin = event.plugin.substr(6); - onMediaStop: function(event) { - console.log('STOP'); - console.log(event); - }, - - onMediaSeek: function(event) { - console.log('SEEK'); - console.log(event); + if (this.status[event.player] && this.status[event.player][event.plugin]) + this.status[event.player][event.plugin] = status; + else if (this.status[event.plugin] && this.status[event.plugin][event.player]) + this.status[event.plugin][event.player] = status; }, }, @@ -112,13 +164,18 @@ Vue.component('media', { MediaHandlers[type].bus = this.bus; } - registerEventHandler(this.onNewPlayingMedia, 'platypush.message.event.media.NewPlayingMediaEvent'); - registerEventHandler(this.onMediaPlay, 'platypush.message.event.media.MediaPlayEvent'); - registerEventHandler(this.onMediaPause, 'platypush.message.event.media.MediaPauseEvent'); - registerEventHandler(this.onMediaStop, 'platypush.message.event.media.MediaStopEvent'); - registerEventHandler(this.onMediaSeek, 'platypush.message.event.media.MediaSeekEvent'); + registerEventHandler(this.onMediaEvent, + 'platypush.message.event.media.NewPlayingMediaEvent', + 'platypush.message.event.media.MediaPlayEvent', + 'platypush.message.event.media.MediaPauseEvent', + 'platypush.message.event.media.MediaStopEvent', + 'platypush.message.event.media.MediaSeekEvent'); this.bus.$on('play', this.play); + this.bus.$on('pause', this.pause); + this.bus.$on('stop', this.stop); + this.bus.$on('seek', this.seek); + this.bus.$on('volume', this.setVolume); this.bus.$on('info', this.info); this.bus.$on('selected-device', this.selectDevice); this.bus.$on('results-loading', this.onResultsLoading); diff --git a/platypush/backend/http/static/js/plugins/media/players/kodi.js b/platypush/backend/http/static/js/plugins/media/players/kodi.js new file mode 100644 index 000000000..5bc92408d --- /dev/null +++ b/platypush/backend/http/static/js/plugins/media/players/kodi.js @@ -0,0 +1,80 @@ +MediaPlayers.kodi = Vue.extend({ + props: { + type: { + type: String, + default: 'kodi', + }, + + device: { + type: Object, + default: () => { + return { + url: undefined, + }; + }, + }, + + accepts: { + type: Object, + default: () => { + return { + youtube: true, + }; + }, + }, + + iconClass: { + type: String, + default: 'fa fa-film', + }, + }, + + computed: { + host: function() { + if (!this.device.url) { + return; + } + + return this.device.url.match(/^https?:\/\/([^:]+):(\d+).*$/)[1]; + }, + + name: function() { + return this.host; + }, + + port: function() { + if (!this.device.url) { + return; + } + + return parseInt(this.device.url.match(/^https?:\/\/([^:]+):(\d+).*$/)[2]); + }, + + text: function() { + return 'Kodi '.concat('[', this.host, ']'); + }, + }, + + methods: { + scan: async function() { + if (!('media.kodi' in __plugins__)) { + return []; + } + + return [ + { url: __plugins__['media.kodi'].url } + ]; + }, + + status: async function() { + return {}; + }, + + play: async function(item) { + }, + + stop: async function() { + }, + }, +}); + diff --git a/platypush/backend/http/static/js/plugins/media/players/local.js b/platypush/backend/http/static/js/plugins/media/players/local.js index 16f60fd9e..354da496d 100644 --- a/platypush/backend/http/static/js/plugins/media/players/local.js +++ b/platypush/backend/http/static/js/plugins/media/players/local.js @@ -67,7 +67,7 @@ MediaPlayers.local = Vue.extend({ ); }, - volume: async function(volume) { + setVolume: async function(volume) { return await request( this.pluginPrefix.concat('.set_volume'), {volume: volume} diff --git a/platypush/backend/http/static/js/plugins/media/results.js b/platypush/backend/http/static/js/plugins/media/results.js index 99dddca3e..75f993d4c 100644 --- a/platypush/backend/http/static/js/plugins/media/results.js +++ b/platypush/backend/http/static/js/plugins/media/results.js @@ -18,6 +18,10 @@ Vue.component('media-results', { type: Object, default: () => {}, }, + resize: { + type: Boolean, + default: false, + }, }, data: function() { @@ -38,6 +42,7 @@ Vue.component('media-results', { return { text: item.text, icon: item.icon, + iconClass: item.iconClass, click: function() { item.action(self.selectedItem); }, diff --git a/platypush/backend/http/static/js/plugins/music.mpd/utils.js b/platypush/backend/http/static/js/plugins/music.mpd/utils.js index 6fc0c496c..5fe84b748 100644 --- a/platypush/backend/http/static/js/plugins/music.mpd/utils.js +++ b/platypush/backend/http/static/js/plugins/music.mpd/utils.js @@ -5,7 +5,7 @@ var utils = { var t = {}; t.h = '' + parseInt(time/3600); t.m = '' + parseInt(time/60 - t.h*60); - t.s = '' + parseInt(time - t.m*60); + t.s = '' + parseInt(time - (t.h*3600 + t.m*60)); for (var attr of ['m','s']) { if (parseInt(t[attr]) < 10) { diff --git a/platypush/backend/http/templates/plugins/media/controls.html b/platypush/backend/http/templates/plugins/media/controls.html index 44ead51da..74e1c9191 100644 --- a/platypush/backend/http/templates/plugins/media/controls.html +++ b/platypush/backend/http/templates/plugins/media/controls.html @@ -9,22 +9,28 @@
-
- - +
-
- +
+ - + :max="status.duration || 0"> +
@@ -34,9 +40,11 @@ + :max="status.volume_max || 100">
diff --git a/platypush/backend/http/templates/plugins/media/index.html b/platypush/backend/http/templates/plugins/media/index.html index 03541db89..0e1232c3e 100644 --- a/platypush/backend/http/templates/plugins/media/index.html +++ b/platypush/backend/http/templates/plugins/media/index.html @@ -27,11 +27,13 @@ :status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}" :searching="loading.results" :loading="loading.media" + :resize="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] && (status[selectedDevice.type][selectedDevice.name].state === 'play' || status[selectedDevice.type][selectedDevice.name].state === 'pause')" :results="results"> + :status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}" + v-if="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] && (status[selectedDevice.type][selectedDevice.name].state === 'play' || status[selectedDevice.type][selectedDevice.name].state === 'pause')"> diff --git a/platypush/backend/http/templates/plugins/media/results.html b/platypush/backend/http/templates/plugins/media/results.html index 0ae9f1bc3..77abbbaa4 100644 --- a/platypush/backend/http/templates/plugins/media/results.html +++ b/platypush/backend/http/templates/plugins/media/results.html @@ -1,7 +1,7 @@