some refactoring
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 27 Jun 2019 19:40:21 +0000 (21:40 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 27 Jun 2019 19:40:21 +0000 (21:40 +0200)
13 files changed:
music_assistant/main.py
music_assistant/modules/homeassistant.py
music_assistant/modules/http_streamer.py
music_assistant/modules/music.py
music_assistant/modules/musicproviders/qobuz.py
music_assistant/modules/player.py
music_assistant/modules/playerproviders/chromecast.py
music_assistant/modules/web.py
music_assistant/temp.flac [deleted file]
music_assistant/web/components/playmenu.vue.js
music_assistant/web/config_old.vue.js [deleted file]
music_assistant/web/index.html
music_assistant/web/strings.js

index 778bedafa69673819162075ed75c4819cd4bfb9a..e9e5530c830ef0e14bb161a93a2df241e2ebdf7a 100755 (executable)
@@ -61,24 +61,31 @@ class Main():
             self.event_loop.run_forever()
         except (KeyboardInterrupt, SystemExit):
             LOGGER.info('Exit requested!')
+            self.signal_event("system_shutdown")
+            self.event_loop.stop()
             self.save_config()
+            time.sleep(5)
             self.event_loop.close()
             LOGGER.info('Shutdown complete.')
 
-    async def event(self, msg, msg_details=None):
-        ''' signal event '''
+    def signal_event(self, msg, msg_details=None):
+        ''' signal (systemwide) event '''
         LOGGER.debug("Event: %s - %s" %(msg, msg_details))
         listeners = list(self.event_listeners.values())
-        for listener in listeners:
-            await listener(msg, msg_details)
+        for callback, eventfilter in listeners:
+            if not eventfilter or eventfilter in msg:
+                if not asyncio.iscoroutinefunction(callback):
+                    callback(msg, msg_details)
+                else:
+                    self.event_loop.create_task(callback(msg, msg_details))
 
-    async def add_event_listener(self, cb):
+    def add_event_listener(self, cb, eventfilter=None):
         ''' add callback to our event listeners '''
         cb_id = str(uuid.uuid4())
-        self.event_listeners[cb_id] = cb
+        self.event_listeners[cb_id] = (cb, eventfilter)
         return cb_id
 
-    async def remove_event_listener(self, cb_id):
+    def remove_event_listener(self, cb_id):
         ''' remove callback from our event listeners '''
         self.event_listeners.pop(cb_id, None)
 
index 28b61f775891fed7b984568b6fce86db6f4fc5f5..32961b47fa72f0e204239a4acfa804c49c64e016 100644 (file)
@@ -72,7 +72,7 @@ class HomeAssistant():
         self.__last_id = 10
         LOGGER.info('Homeassistant integration is enabled')
         mass.event_loop.create_task(self.__hass_websocket())
-        mass.event_loop.create_task(self.mass.add_event_listener(self.mass_event))
+        self.mass.add_event_listener(self.mass_event, "player updated")
         mass.event_loop.create_task(self.__get_sources())
 
     async def get_state(self, entity_id, attribute='state', register_listener=None):
index fcb6ccbf9cca2b664f69f239992056bbb6520a9a..19ff243dc20802131e338699e85f19187a98ea96 100755 (executable)
@@ -196,13 +196,13 @@ class HTTPStreamer():
                 queue_tracks = await self.mass.player.player_queue(player_id, queue_index, queue_index+1)
                 queue_track = queue_tracks[0]
             except IndexError:
-                LOGGER.info("queue index out of range or end reached")
+                LOGGER.warning("queue index out of range or end reached")
                 break
 
             params = urllib.parse.parse_qs(queue_track.uri.split('?')[1])
             track_id = params['track_id'][0]
             provider = params['provider'][0]
-            LOGGER.info("Start Streaming queue track: %s (%s) on player %s" % (track_id, queue_track.name, player.name))
+            LOGGER.debug("Start Streaming queue track: %s (%s) on player %s" % (track_id, queue_track.name, player.name))
             fade_in_part = b''
             cur_chunk = 0
             prev_chunk = None
@@ -248,7 +248,10 @@ class HTTPStreamer():
                             stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
                     last_part, stderr = await process.communicate(prev_chunk + chunk)
                     if len(last_part) < fade_bytes:
-                        # not enough data for crossfade duration
+                        # not enough data for crossfade duration after the strip action...
+                        last_part = prev_chunk + chunk
+                    if len(last_part) < fade_bytes:
+                        # still not enough data so we'll skip the crossfading
                         LOGGER.warning("not enough data for fadeout so skip crossfade... %s" % len(last_part))
                         sox_proc.stdin.write(last_part)
                         bytes_written += len(last_part)
@@ -291,7 +294,7 @@ class HTTPStreamer():
                 # move to next queue index
                 queue_index += 1
                 self.mass.event_loop.create_task(self.mass.player.player_queue_stream_update(player_id, queue_index, False))
-                LOGGER.info("Finished Streaming queue track: %s (%s) on player %s" % (track_id, queue_track.name, player.name))
+                LOGGER.debug("Finished Streaming queue track: %s (%s) on player %s" % (track_id, queue_track.name, player.name))
         # end of queue reached, pass last fadeout bits to final output
         if last_fadeout_data and not cancelled.is_set():
             sox_proc.stdin.write(last_fadeout_data)
@@ -315,15 +318,18 @@ class HTTPStreamer():
             sox_effects += ' rate -v %s' % resample
         # stream audio from provider
         streamdetails = asyncio.run_coroutine_threadsafe(
-                self.mass.music.providers[provider].get_stream_details(track_id), self.mass.event_loop).result()
+                self.mass.music.providers[provider].get_stream_details(track_id), 
+                self.mass.event_loop).result()
         if not streamdetails:
             yield (True, b'')
             return
         # TODO: add support for AAC streams (which sox doesn't natively support)
         if streamdetails['type'] == 'url':
-            args = 'sox -t %s "%s" -t %s - %s %s' % (streamdetails["content_type"], streamdetails["path"], outputfmt, gain_correct, sox_effects)
+            args = 'sox -t %s "%s" -t %s - %s %s' % (streamdetails["content_type"], 
+                    streamdetails["path"], outputfmt, gain_correct, sox_effects)
         elif streamdetails['type'] == 'executable':
-            args = '%s | sox -t %s - -t %s - %s %s' % (streamdetails["path"], streamdetails["content_type"], outputfmt, gain_correct, sox_effects)
+            args = '%s | sox -t %s - -t %s - %s %s' % (streamdetails["path"], 
+                    streamdetails["content_type"], outputfmt, gain_correct, sox_effects)
         LOGGER.debug("Running sox with args: %s" % args)
         process = await asyncio.create_subprocess_shell(args,
                 stdout=asyncio.subprocess.PIPE)
@@ -331,7 +337,7 @@ class HTTPStreamer():
         streamdetails["provider"] = provider
         streamdetails["track_id"] = track_id
         streamdetails["player_id"] = player_id
-        self.mass.event_loop.create_task(self.mass.event('streaming_started', streamdetails))
+        self.mass.signal_event('streaming_started', streamdetails)
         # yield chunks from stdout
         # we keep 1 chunk behind to detect end of stream properly
         prev_chunk = b''
@@ -355,7 +361,7 @@ class HTTPStreamer():
         if cancelled.is_set():
             LOGGER.warning("__get_audio_stream for track_id %s interrupted" % track_id)
         else:
-            LOGGER.info("__get_audio_stream for track_id %s completed" % track_id)
+            LOGGER.debug("__get_audio_stream for track_id %s completed" % track_id)
         # fire event that streaming has ended for this track (needed by some streaming providers)
         if resample:
             bytes_per_second = resample * (32/8) * 2
@@ -363,7 +369,7 @@ class HTTPStreamer():
             bytes_per_second = streamdetails["sample_rate"] * (streamdetails["bit_depth"]/8) * 2
         seconds_streamed = int(bytes_sent/bytes_per_second)
         streamdetails["seconds"] = seconds_streamed
-        self.mass.event_loop.create_task(self.mass.event('streaming_ended', streamdetails))
+        self.mass.signal_event('streaming_ended', streamdetails)
         # send task to background to analyse the audio
         self.mass.event_loop.create_task(self.__analyze_audio(track_id, provider))
 
index 04b62f22250c3b39f094acd6d9bc2319b0dd3fd0..ad4bdc14865e608195aa38b0ed4a140aa1c31154 100755 (executable)
@@ -195,11 +195,11 @@ class Music():
                 items = list(toolz.unique(items, key=operator.attrgetter('item_id')))
         return result
 
-    async def item_action(self, item_id, media_type, provider='database', action=None):
+    async def item_action(self, item_id, media_type, provider, action, action_details=None):
         ''' perform action on item (such as library add/remove) '''
         result = None
         item = await self.item(item_id, media_type, provider)
-        if item and action in ['add', 'remove']:
+        if item and action in ['library_add', 'library_remove']:
             # remove or add item to the library
             for prov_mapping in result.provider_ids:
                 prov_id = prov_mapping['provider']
index 4157093022c9e471e85a23df27e38b4cad231b58..b1051fa33c3bc133fa266115c7d3cd54f7c3fb60 100644 (file)
@@ -50,7 +50,8 @@ class QobuzProvider(MusicProvider):
         self.__app_secret = "47249d0eaefa6bf43a959c09aacdbce8" # TEMP! Own key requested
         self.__logged_in = False
         self.throttler = Throttler(rate_limit=2, period=1)
-        mass.event_loop.create_task(mass.add_event_listener(self.mass_event))
+        mass.add_event_listener(self.mass_event, 'streaming_started')
+        mass.add_event_listener(self.mass_event, 'streaming_ended')
 
     async def search(self, searchstring, media_types=List[MediaType], limit=5):
         ''' perform search on the provider '''
@@ -262,8 +263,6 @@ class QobuzProvider(MusicProvider):
             streamdetails = await self.__get_data('track/getFileUrl', params, sign_request=True, ignore_cache=True)
             if streamdetails and streamdetails.get('url'):
                 break
-            else:
-                await asyncio.sleep(1)
         if not streamdetails or not streamdetails.get('url'):
             LOGGER.error("Unable to retrieve stream url for track %s" % track_id)
             return None
@@ -305,7 +304,7 @@ class QobuzProvider(MusicProvider):
             await self.__post_data("track/reportStreamingStart", data=events)
     
     async def __parse_artist(self, artist_obj):
-        ''' parse spotify artist object to generic layout '''
+        ''' parse qobuz artist object to generic layout '''
         artist = Artist()
         if not artist_obj.get('id'):
             return None
@@ -329,7 +328,7 @@ class QobuzProvider(MusicProvider):
         return artist
 
     async def __parse_album(self, album_obj):
-        ''' parse spotify album object to generic layout '''
+        ''' parse qobuz album object to generic layout '''
         album = Album()
         if not album_obj.get('id') or not album_obj["streamable"] or not album_obj["displayable"]:
             # some safety checks
@@ -375,7 +374,7 @@ class QobuzProvider(MusicProvider):
         return album
 
     async def __parse_track(self, track_obj):
-        ''' parse spotify track object to generic layout '''
+        ''' parse qobuz track object to generic layout '''
         track = Track()
         if not track_obj.get('id') or not track_obj["streamable"] or not track_obj["displayable"]:
             # some safety checks
@@ -450,7 +449,7 @@ class QobuzProvider(MusicProvider):
         return track
 
     async def __parse_playlist(self, playlist_obj):
-        ''' parse spotify playlist object to generic layout '''
+        ''' parse qobuz playlist object to generic layout '''
         playlist = Playlist()
         if not playlist_obj.get('id'):
             return None
@@ -538,8 +537,8 @@ class QobuzProvider(MusicProvider):
                     result = await response.json()
                     if not result or 'error' in result:
                         LOGGER.error(url)
-                        LOGGER.error(params)
-                        LOGGER.error(result)
+                        LOGGER.debug(params)
+                        LOGGER.debug(result)
                         return None
                     return result
         except Exception as exc:
@@ -555,7 +554,7 @@ class QobuzProvider(MusicProvider):
             result = await response.json()
             if not result or 'error' in result:
                 LOGGER.error(url)
-                LOGGER.error(params)
-                LOGGER.error(result)
+                LOGGER.debug(params)
+                LOGGER.debug(result)
                 result = None
             return result
\ No newline at end of file
index 52fd7a68188a0de2f6745c4b32e38ba4d9ac59b4..da0e50a48d14b8a6906d2309a6cee3daa836ddd4 100755 (executable)
@@ -156,7 +156,7 @@ class Player():
     async def remove_player(self, player_id):
         ''' handle a player remove '''
         self._players.pop(player_id, None)
-        asyncio.ensure_future(self.mass.event('player removed', player_id))
+        self.mass.signal_event('player removed', player_id)
 
     async def trigger_update(self, player_id):
         ''' manually trigger update for a player '''
@@ -212,7 +212,7 @@ class Player():
                     LOGGER.debug('key changed: %s for player %s - new value: %s' % (key, player.name, new_value))
         if player_changed:
             # player is added or updated!
-            asyncio.ensure_future(self.mass.event('player updated', player))
+            self.mass.signal_event('player updated', player)
             if player_details.is_group:
                 # is groupplayer, trigger update of its childs
                 player_childs = [item for item in self._players.values() if item.group_parent == player_id]
index 877235df3dabb9a108a2b15733a65a6642e56b2b..00776259cbe82f428f3f2b33ae6aad963e14f11b 100644 (file)
@@ -55,7 +55,11 @@ class ChromecastProvider(PlayerProvider):
         self._player_queue_index = {}
         self._player_queue_stream_startindex = {}
         self.supported_musicproviders = ['http']
-        run_background_task(self.mass.bg_executor, self.__chromecast_discovery)
+        abort_discovery = self.__chromecast_discovery()
+        def on_shutdown(msg, msg_details):
+            LOGGER.info('stopping Chromecast discovery...')
+            abort_discovery()
+        mass.add_event_listener(on_shutdown, 'system_shutdown')
         
     ### Provider specific implementation #####
 
@@ -395,14 +399,16 @@ class ChromecastProvider(PlayerProvider):
         from pychromecast.discovery import start_discovery, stop_discovery
         def internal_callback(name):
             """Called when zeroconf has discovered a new chromecast."""
-            self.__chromecast_discovered(listener.services[name])
+            #self.__chromecast_discovered(listener.services[name])
+            asyncio.run_coroutine_threadsafe(
+                    self.__chromecast_discovered(listener.services[name]), self.mass.event_loop)
         def internal_stop():
             """Stops discovery of new chromecasts."""
             stop_discovery(browser)
         listener, browser = start_discovery(internal_callback)
         return internal_stop
     
-    def __chromecast_discovered(self, discovery_info):
+    async def __chromecast_discovered(self, discovery_info):
         ''' callback when a (new) chromecast device is discovered '''
         ip_address, port, uuid, model_name, friendly_name = discovery_info
         player_id = str(uuid)
index 033ba947b7b27bbda357e1bdbccd54200a34aeea..7701d7f1c5cdd4b224ae958aeacd6155d8e81b48 100755 (executable)
@@ -119,10 +119,11 @@ class Web():
         media_type = media_type_from_string(media_type_str)
         media_id = request.match_info.get('media_id')
         action = request.match_info.get('action','')
+        action_details = request.rel_url.query.get('action_details')
         lazy = request.rel_url.query.get('lazy', '') != 'false'
         provider = request.rel_url.query.get('provider')
         if action:
-            result = await self.mass.music.item_action(media_id, media_type, provider, action)
+            result = await self.mass.music.item_action(media_id, media_type, provider, action, action_details)
         else:
             result = await self.mass.music.item(media_id, media_type, provider, lazy=lazy)
         return web.json_response(result, dumps=json_serializer)
@@ -225,7 +226,7 @@ class Web():
             async def send_event(msg, msg_details):
                 ws_msg = {"message": msg, "message_details": msg_details }
                 await ws.send_json(ws_msg, dumps=json_serializer)
-            cb_id = await self.mass.add_event_listener(send_event)
+            cb_id = self.mass.add_event_listener(send_event)
             # process incoming messages
             async for msg in ws:
                 if msg.type != aiohttp.WSMsgType.TEXT:
@@ -243,8 +244,8 @@ class Web():
                     cmd_args = msg_data_parts[4] if len(msg_data_parts) == 5 else None
                     await self.mass.player.player_command(player_id, cmd, cmd_args)
         finally:
-            await self.mass.remove_event_listener(cb_id)
-        LOGGER.info('websocket connection closed')
+            self.mass.remove_event_listener(cb_id)
+        LOGGER.debug('websocket connection closed')
         return ws
 
     async def get_config(self, request):
@@ -252,17 +253,24 @@ class Web():
         return web.json_response(self.mass.config)
 
     async def save_config(self, request):
-        ''' save the config '''
+        ''' save (partial) config '''
         LOGGER.debug('save config called from api')
         new_config = await request.json()
+        config_changed = False
         for key, value in self.mass.config.items():
             if isinstance(value, dict):
                 for subkey, subvalue in value.items():
                     if subkey in new_config[key]:
-                        self.mass.config[key][subkey] = new_config[key][subkey]
+                        if self.mass.config[key][subkey] != new_config[key][subkey]:
+                            config_changed = True
+                            self.mass.config[key][subkey] = new_config[key][subkey]
             elif key in new_config:
-                self.mass.config[key] = new_config[key]
-        self.mass.save_config()
+                if self.mass.config[key] != new_config[key]:
+                    config_changed = True
+                    self.mass.config[key] = new_config[key]
+        if config_changed:
+            self.mass.save_config()
+            self.mass.signal_event('config_changed')
         return web.Response(text='success')
 
     async def json_rpc(self, request):
@@ -298,5 +306,7 @@ class Web():
             await self.mass.player.player_command(player_id, 'volume', 'down')
         elif cmd_str == 'button power':
             await self.mass.player.player_command(player_id, 'power', 'toggle')
+        else:
+            return web.Response(text='command not supported')
         return web.Response(text='success')
         
\ No newline at end of file
diff --git a/music_assistant/temp.flac b/music_assistant/temp.flac
deleted file mode 100644 (file)
index add5839..0000000
Binary files a/music_assistant/temp.flac and /dev/null differ
index cbe4433b48ae16c118babbfff77ee323ee871e6f..611ecc36a0b469f651d1d9dcd9a1c27282011fb3 100644 (file)
@@ -3,7 +3,7 @@ Vue.component("playmenu", {
        <v-dialog :value="value" @input="$emit('input', $event)" max-width="500px" v-if="$globals.playmenuitem">\r
         <v-card>\r
                <v-list>\r
-               <v-subheader class="title">{{ !!$globals.playmenuitem ? $globals.playmenuitem.name : 'nix' }}</v-subheader>\r
+               <v-subheader class="title">{{ !!$globals.playmenuitem ? $globals.playmenuitem.name : '' }}</v-subheader>\r
                        <v-subheader>{{ $t('play_on') }} {{ active_player.name }}</v-subheader>\r
                        \r
                        <v-list-tile avatar @click="itemClick('play')">\r
@@ -45,6 +45,26 @@ Vue.component("playmenu", {
                                </v-list-tile-content>\r
                        </v-list-tile>\r
                        <v-divider v-if="$globals.playmenuitem.media_type == 3"/>\r
+\r
+                       <v-list-tile avatar @click="itemClick('add_playlist')" v-if="$globals.playmenuitem.media_type == 3">\r
+                               <v-list-tile-avatar>\r
+                                       <v-icon>add_circle_outline</v-icon>\r
+                               </v-list-tile-avatar>\r
+                               <v-list-tile-content>\r
+                                       <v-list-tile-title>{{ $t('add_playlist') }}</v-list-tile-title>\r
+                               </v-list-tile-content>\r
+                       </v-list-tile>\r
+                       <v-divider v-if="$globals.playmenuitem.media_type == 3"/>\r
+\r
+                       <v-list-tile avatar @click="itemClick('remove_playlist')" v-if="$globals.playmenuitem.media_type == 3 && this.$route.path.startsWith('/playlists/')">\r
+                               <v-list-tile-avatar>\r
+                                       <v-icon>remove_circle_outline</v-icon>\r
+                               </v-list-tile-avatar>\r
+                               <v-list-tile-content>\r
+                                       <v-list-tile-title>{{ $t('remove_playlist') }}</v-list-tile-title>\r
+                               </v-list-tile-content>\r
+                       </v-list-tile>\r
+                       <v-divider v-if="$globals.playmenuitem.media_type == 3  && this.$route.path.startsWith('/playlists/')"/>\r
                        \r
                </v-list>\r
         </v-card>\r
@@ -62,12 +82,12 @@ Vue.component("playmenu", {
        created() { },\r
        methods: { \r
                itemClick(cmd) {\r
-      if (cmd == 'info')\r
+               if (cmd == 'info')\r
                                this.$router.push({ path: '/tracks/' + this.$globals.playmenuitem.item_id, query: {provider: this.$globals.playmenuitem.provider}})\r
                        else\r
                                this.$emit('playItem', this.$globals.playmenuitem, cmd)\r
                        // close dialog\r
                        this.$globals.showplaymenu = false;\r
-    },\r
+       },\r
        }\r
   })\r
diff --git a/music_assistant/web/config_old.vue.js b/music_assistant/web/config_old.vue.js
deleted file mode 100755 (executable)
index 77299c2..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-var Config = Vue.component('Config', {
-  template: `
-    <section>
-
-      <v-list two-line>
-
-        <!-- base/generic config -->
-        <v-list-group prepend-icon="settings" no-action>
-            <template v-slot:activator>
-              <v-list-tile>
-                <v-list-tile-content>
-                  <v-list-tile-title>{{ $t('generic_settings') }}</v-list-tile-title>
-                </v-list-tile-content>
-              </v-list-tile>
-            </template>
-            <template v-for="(conf_value, conf_key) in conf.base">
-                <v-list-tile>
-                  <v-list-tile-content>
-                    <v-list-tile-title class="title">{{ conf_key }}</v-list-tile-title>
-                  </v-list-tile-content>
-                </v-list-tile>
-                
-                <div v-for="conf_item_key in conf.base[conf_key].__desc__">
-                  <v-list-tile>
-                        <v-switch v-if="typeof(conf_item_key[1]) == 'boolean'" v-model="conf.base[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])"></v-switch>
-                        <v-text-field v-else-if="conf_item_key[1] == '<password>'" v-model="conf.base[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-text-field>
-                        <v-select v-else-if="conf_item_key[1] == '<player>'" v-model="conf.base[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-select>
-                        <v-text-field v-else v-model="conf.base[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box></v-text-field>
-                  </v-list-tile>
-              </div>
-              <v-divider></v-divider>
-            </template>
-          </v-list-group>
-
-
-          <!-- music providers -->
-          <v-list-group prepend-icon="library_music" no-action>
-              <template v-slot:activator>
-                <v-list-tile>
-                  <v-list-tile-content>
-                    <v-list-tile-title>{{ $t('music_providers') }}</v-list-tile-title>
-                  </v-list-tile-content>
-                </v-list-tile>
-              </template>
-              <template v-for="(conf_value, conf_key) in conf.musicproviders">
-                  <v-list-tile>
-                    <v-list-tile-avatar>
-                        <img :src="'images/icons/' + conf_key + '.png'"/>
-                    </v-list-tile-avatar>
-                    <v-list-tile-content>
-                      <v-list-tile-title class="title">{{ conf_key }}</v-list-tile-title>
-                    </v-list-tile-content>
-                  </v-list-tile>
-                  
-                  <div v-for="conf_item_key in conf.musicproviders[conf_key].__desc__">
-                    <v-list-tile>
-                          <v-switch v-if="typeof(conf_item_key[1]) == 'boolean'" v-model="conf.musicproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])"></v-switch>
-                          <v-text-field v-else-if="conf_item_key[1] == '<password>'" v-model="conf.musicproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-text-field>
-                          <v-select v-else-if="conf_item_key[1] == '<player>'" v-model="conf.musicproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-select>
-                          <v-text-field v-else v-model="conf.musicproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box></v-text-field>
-                    </v-list-tile>
-                </div>
-                <v-divider></v-divider>
-              </template>
-            </v-list-group>
-
-          <!-- player providers -->
-          <v-list-group prepend-icon="speaker_group" no-action>
-              <template v-slot:activator>
-                <v-list-tile>
-                  <v-list-tile-content>
-                    <v-list-tile-title>{{ $t('player_providers') }}</v-list-tile-title>
-                  </v-list-tile-content>
-                </v-list-tile>
-              </template>
-              <template v-for="(conf_value, conf_key) in conf.playerproviders">
-                  <v-list-tile>
-                    <v-list-tile-avatar>
-                        <img :src="'images/icons/' + conf_key + '.png'"/>
-                    </v-list-tile-avatar>
-                    <v-list-tile-content>
-                      <v-list-tile-title class="title">{{ conf_key }}</v-list-tile-title>
-                    </v-list-tile-content>
-                  </v-list-tile>
-                  
-                  <div v-for="conf_item_key in conf.playerproviders[conf_key].__desc__">
-                    <v-list-tile>
-                          <v-switch v-if="typeof(conf_item_key[1]) == 'boolean'" v-model="conf.playerproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])"></v-switch>
-                          <v-text-field v-else-if="conf_item_key[1] == '<password>'" v-model="conf.playerproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-text-field>
-                          <v-select v-else-if="conf_item_key[1] == '<player>'" v-model="conf.playerproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-select>
-                          <v-text-field v-else v-model="conf.playerproviders[conf_key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box></v-text-field>
-                    </v-list-tile>
-                </div>
-                <v-divider></v-divider>
-              </template>
-            </v-list-group>
-
-          <!-- player settings -->
-          <v-list-group prepend-icon="speaker" no-action>
-              <template v-slot:activator>
-                <v-list-tile>
-                  <v-list-tile-content>
-                    <v-list-tile-title>{{ $t('player_settings') }}</v-list-tile-title>
-                  </v-list-tile-content>
-                </v-list-tile>
-              </template>
-              <template v-for="(player, key) in players" v-if="key != '__desc__' && key in players">
-                  <v-list-tile>
-                    <v-list-tile-content>
-                      <v-list-tile-title class="title">{{ players[key].name }}</v-list-tile-title>
-                      <v-list-tile-sub-title class="title">ID: {{ key }} Provider: {{ players[key].player_provider }}</v-list-tile-sub-title>
-                    </v-list-tile-content>
-                  </v-list-tile>
-                  
-                  <div v-for="conf_item_key in conf.player_settings.__desc__" v-if="conf.player_settings[key].enabled">
-                    <v-list-tile>
-                          <v-switch v-if="typeof(conf_item_key[1]) == 'boolean'" v-model="conf.player_settings[key][conf_item_key[0]]" :label="$t(conf_item_key[2])"></v-switch>
-                          <v-text-field v-else-if="conf_item_key[1] == '<password>'" v-model="conf.player_settings[key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box type="password"></v-text-field>
-                          <v-select v-else-if="conf_item_key[1] == '<player>'" v-model="conf.player_settings[key][conf_item_key[0]]" :label="$t(conf_item_key[2])" 
-                            :items="playersLst"
-                            item-text="name"
-                            item-value="id" box>
-                          </v-select>
-                          <v-text-field v-else v-model="conf.player_settings[key][conf_item_key[0]]" :label="$t(conf_item_key[2])" box></v-text-field>
-                    </v-list-tile>
-                    <v-list-tile v-if="!conf.player_settings[key].enabled">
-                          <v-switch v-model="conf.player_settings[key].enabled" :label="$t('enabled')"></v-switch>
-                    </v-list-tile>
-                </div>
-                <div v-if="!conf.player_settings[key].enabled">
-                    <v-list-tile>
-                        <v-switch v-model="conf.player_settings[key].enabled" :label="$t('enabled')"></v-switch>
-                    </v-list-tile>
-                </div>
-                <v-divider></v-divider>
-              </template>
-            </v-list-group>
-
-            <v-btn @click="saveConfig()">Save</v-btn>
-        </v-list>
-    </section>
-  `,
-  props: [],
-  data() {
-    return {
-      conf: {},
-      players: {}
-    }
-  },
-  computed: {
-    playersLst()
-    {
-      var playersLst = [];
-      for (player_id in this.conf.player_settings)
-        if (player_id != '__desc__')
-          playersLst.push({id: player_id, name: this.conf.player_settings[player_id].name})
-      return playersLst;
-    }
-
-  },
-  created() {
-    this.$globals.windowtitle = this.$t('settings');
-    this.getPlayers();
-    this.getConfig();
-    console.log(this.$globals.all_players);
-  },
-  methods: {
-    getConfig () {
-      axios
-        .get('/api/config')
-        .then(result => {
-          this.conf = result.data;
-        })
-        .catch(error => {
-          console.log("error", error);
-        });
-    },
-    saveConfig () {
-      axios
-        .post('/api/config', this.conf)
-        .then(result => {
-          console.log(result);
-        })
-        .catch(error => {
-          console.log("error", error);
-        });
-    },
-    getPlayers () {
-      const api_url = '/api/players';
-      axios
-        .get(api_url)
-        .then(result => {
-          for (var item of result.data)
-            this.$set(this.players, item.player_id, item)
-        })
-        .catch(error => {
-          console.log("error", error);
-          this.showProgress = false;
-        });
-    },
-  }
-})
index 14a90a3d4c202569e252539d763566c58000a472..0584f5c4d4ccba8da07d60c11ed0b3d082043cbb 100755 (executable)
@@ -86,9 +86,9 @@
             function toggleLibrary (item) {
                 var endpoint = "/api/" + item.media_type + "/";
                 item_id = item.item_id.toString();
-                var action = "/remove"
+                var action = "/library_remove"
                 if (item.in_library.length == 0)
-                    action = "/add"
+                    action = "/library_add"
                 var url = endpoint + item_id + action;
                 console.log('loading ' + url);
                 axios
@@ -96,7 +96,7 @@
                     .then(result => {
                         data = result.data;
                         console.log(data);
-                        if (action == "/remove")
+                        if (action == "/library_remove")
                             item.in_library = []
                         else
                         item.in_library = [provider]
index b4011f6adcabe6257d44f8ca300b4817b778fc12..5c1df07628d94eb900891de20b1265e0b97f7635 100644 (file)
@@ -16,6 +16,8 @@ const messages = {
         type_to_search: "Type here to search...",
         add_library: "Add to library",
         remove_library: "Remove from library",
+        add_playlist: "Add to playlist...",
+        remove_playlist: "Remove from playlist",
         // settings strings
         conf: {
             enabled: "Enabled",
@@ -102,6 +104,8 @@ const messages = {
         type_to_search: "Type hier om te zoeken...",
         add_library: "Voeg toe aan bibliotheek",
         remove_library: "Verwijder uit bibliotheek",
+        add_playlist: "Aan playlist toevoegen...",
+        remove_playlist: "Verwijder uit playlist",
         // settings strings
         conf: {
             enabled: "Ingeschakeld",