Browse Source

fix: Adding webserver and proper playlist processing

Matthias Ladkau 4 years ago
parent
commit
6512fc229a

+ 12 - 5
musikautomat/display.py

@@ -22,7 +22,7 @@ class Display:
         self._playlistFile = config.get("playlist", "music.yml") # Playlist data
         self._playlistFile = config.get("playlist", "music.yml") # Playlist data
         self._pydsp = pydsp # Pygame display object
         self._pydsp = pydsp # Pygame display object
         self._fontsize = config.get("fontsize", 12) # Fontsize to use
         self._fontsize = config.get("fontsize", 12) # Fontsize to use
-        self._drawlines = int(self._dy / 12) - 1 # Max number of drawn lines
+        self._drawlines = int(self._dy / 12) - 5 # Max number of drawn lines
 
 
         # Set initialisation values
         # Set initialisation values
 
 
@@ -31,9 +31,9 @@ class Display:
         self._current_item = None
         self._current_item = None
 
 
 
 
-    def loadPlaylist(self) -> Dict:
+    def getPlaylist(self) -> Dict:
         '''
         '''
-        Load the playlist.
+        Return the playlist for this display.
         '''
         '''
         return yaml.safe_load(open(self._playlistFile)).get("playlist", [])
         return yaml.safe_load(open(self._playlistFile)).get("playlist", [])
 
 
@@ -43,7 +43,7 @@ class Display:
         Load the data from the given playlist file and draw it according to the current state.
         Load the data from the given playlist file and draw it according to the current state.
         '''
         '''
         self._pydsp.fill((0,0,0))
         self._pydsp.fill((0,0,0))
-        playlist = self.loadPlaylist()
+        playlist = self.getPlaylist()
 
 
         # Correct offset and selection pointer
         # Correct offset and selection pointer
 
 
@@ -72,11 +72,18 @@ class Display:
 
 
             if drawline == self._selection_pointer:
             if drawline == self._selection_pointer:
                 text = font.render(item["name"], True, BLACK, GREEN)
                 text = font.render(item["name"], True, BLACK, GREEN)
+                img = item.get("img")
+
+                if img is not None:
+                    pass
+                else:
+                    pygame.draw.rect(self._pydsp, GREEN, (0,0,self._dx,50))
+
                 self._current_item = item
                 self._current_item = item
             else:
             else:
                 text = font.render(item["name"], True, GREEN, BLACK)
                 text = font.render(item["name"], True, GREEN, BLACK)
             
             
-            self._pydsp.blit(text, (0, drawline * self._fontsize))
+            self._pydsp.blit(text, (0, drawline * self._fontsize+50))
             
             
             # Increase number of drawn lines - stop when the maximum is reached
             # Increase number of drawn lines - stop when the maximum is reached
 
 

+ 5 - 86
musikautomat/main.py

@@ -1,8 +1,8 @@
 #!./bin/python3
 #!./bin/python3
 
 
-import sys, os, time
-import traceback
+import os
 import yaml
 import yaml
+import threading
 
 
 # Include pygame without support prompt
 # Include pygame without support prompt
 # Typing only available from version >= 2.0
 # Typing only available from version >= 2.0
@@ -10,8 +10,7 @@ import yaml
 os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
 os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
 import pygame # type: ignore
 import pygame # type: ignore
 
 
-from display import Display
-from player import Player
+from musikautomat import Musikautomat
 
 
 # Configuration
 # Configuration
 
 
@@ -33,85 +32,5 @@ pygame.display.set_caption("Musikautomat")
 pygame.mouse.set_visible(False)
 pygame.mouse.set_visible(False)
 pygame.key.set_repeat(400, 100)
 pygame.key.set_repeat(400, 100)
 
 
-# Initialise state objects
-
-display = Display(config, pydsp)
-display.update()
-eventSink = display
-player = Player(config, pydsp)
-clk = pygame.time.Clock()
-
-# Run the game loop
-
-while True:
-    clk.tick(10) # We only need 10 FPS
-
-    try:
-
-        for event in pygame.event.get():
-            
-            # Handle exit event
-            
-            if event.type == pygame.QUIT:
-                pygame.quit()
-                sys.exit()
-            
-            # Handle key events
-
-            if event.type == pygame.KEYDOWN:
-
-                if event.key == pygame.K_DOWN:
-                    eventSink.action("down")
-
-                elif event.key == pygame.K_UP:
-                    eventSink.action("up")
-
-                elif event.key == pygame.K_RIGHT:
-
-                    # Activate player
-
-                    item = display.currentItem()
-                    eventSink = player
-                    
-                    # TODO File selection depending on type
-                    path = item.get("path")
-                    files = os.listdir(path)
-
-                    player.setData([{ 
-                        "name" : f,
-                        "path" : os.path.join(path, f)
-                    } for f in files])
-
-                    player.update()
-
-                elif event.key == pygame.K_LEFT:
-
-                    # Get back to display
-
-                    eventSink = display
-                    display.update()
-
-                elif event.key == pygame.K_RETURN:
-
-                    # Play / pause current item
-
-                    player.togglePlay()
-
-
-    except Exception as e:
-        
-        # Display error
-        traceback.print_exc()
-
-        pydsp.fill((0,0,0))
-        font = pygame.font.Font('freesansbold.ttf', 12)
-        
-        s = str(e)
-        n = int(dx / 6)
-        for i, start in enumerate(range(0, len(s), n)):
-            text = font.render(s[start:start+n], True, (255, 0, 0) , (0, 0, 0))
-            pydsp.blit(text, (0, i * 12))
-        
-        eventSink = display
-
-    pygame.display.update()
+ma = Musikautomat(pydsp, config)
+ma.runDisplay()

+ 3 - 0
musikautomat/music.yml

@@ -8,6 +8,9 @@ playlist:
   - name: ACDC
   - name: ACDC
     type: dir
     type: dir
     path: /home/ml/Music/acdc
     path: /home/ml/Music/acdc
+  - name: TesT
+    type: dir
+    path: /home/ml/Music/test
   - name: Pantera1
   - name: Pantera1
     type: dir
     type: dir
     path: foo
     path: foo

+ 156 - 0
musikautomat/musikautomat.py

@@ -0,0 +1,156 @@
+#!./bin/python3
+
+import os
+import sys
+import threading
+import time
+import traceback
+from typing import Dict
+
+import pygame # type: ignore
+import tornado.ioloop, tornado.web
+import asyncio
+
+from display import Display
+from player import Player
+
+class WebHandler(tornado.web.RequestHandler):
+
+    def initialize(self, musikautomat):
+        self.musikautomat = musikautomat
+
+    def get(self, a):
+        if a.startswith("play"):
+
+            if a == "play/dir":
+                path = self.get_argument('path')
+                self.musikautomat.play({
+                    "type" : "dir",
+                    "name" : self.get_argument('name', path),
+                    "path" : path
+                }, start=True)
+
+class Musikautomat:
+
+    def __init__(self, pydsp, config: Dict):
+
+        # Initialise state objects
+
+        self._pydsp = pydsp
+        self._display = Display(config, self._pydsp)
+        self._display.update()
+        self._eventSink = self._display
+        self._player = Player(config, self._pydsp)
+        self._dx = config.get("dimx", 128)
+
+        threading.Thread(target=self.runWebServer, daemon=True).start()
+
+
+    def runWebServer(self):
+        '''
+        Run event loop for web interface.
+        '''
+        urls = [
+            (r"/api/(.*)", WebHandler, dict(musikautomat=self)),
+            (r"/(.*)", tornado.web.StaticFileHandler, {"path" : "./web",
+            "default_filename" : "index.html" }),
+        ]
+ 
+        asyncio.set_event_loop(asyncio.new_event_loop())
+        app = tornado.web.Application(urls, debug=False)
+        app.listen(8080)
+        tornado.ioloop.IOLoop.instance().start()
+
+
+    def runDisplay(self):
+        '''
+        Run event loop for display and buttons.
+        '''
+        clk = pygame.time.Clock()
+        while True:
+            clk.tick(10) # We only need 10 FPS
+
+            try:
+
+                for event in pygame.event.get():
+                    
+                    # Handle exit event
+                    
+                    if event.type == pygame.QUIT:
+                        pygame.quit()
+                        sys.exit()
+                    
+                    # Handle key events
+
+                    if event.type == pygame.KEYDOWN:
+
+                        if event.key == pygame.K_DOWN:
+                            self._eventSink.action("down")
+
+                        elif event.key == pygame.K_UP:
+                            self._eventSink.action("up")
+
+                        elif event.key == pygame.K_RIGHT:
+
+                            if self._eventSink == self._player:
+                                self._player.togglePlay()                                
+                            else:
+                                self.play(self._display.currentItem())
+
+                        elif event.key == pygame.K_LEFT:
+
+                            # Get back to display
+
+                            self._eventSink = self._display
+                            self._display.update()
+
+            except Exception as e:
+                
+                # Display error
+                traceback.print_exc()
+
+                self._pydsp.fill((0,0,0))
+                font = pygame.font.Font('freesansbold.ttf', 12)
+                
+                s = str(e)
+                n = int(self._dx / 6)
+                for i, start in enumerate(range(0, len(s), n)):
+                    text = font.render(s[start:start+n], True, (255, 0, 0) , (0, 0, 0))
+                    self._pydsp.blit(text, (0, i * 12))
+                
+                self._eventSink = self._display
+
+            pygame.display.update()
+
+
+    def play(self, item, start=False):
+        '''
+        Play an item.
+        '''
+        print("Playing:", item)
+
+        self._eventSink = self._player
+
+        t = item.get("type")
+        if t == "dir":
+            self.playDir(item)
+        else:
+            raise Exception("Unknown type: %s" % t)
+
+        if start:
+            self._player.togglePlay()                                
+
+
+    def playDir(self, item):
+        '''
+        Play all files in a directory.
+        '''
+        path = item.get("path")
+        files = os.listdir(path)
+
+        self._player.setPlaylist(sorted([{ 
+            "name" : f,
+            "path" : os.path.join(path, f)
+        } for f in files], key=lambda i: i["name"]))
+
+        self._player.update()

+ 83 - 6
musikautomat/player.py

@@ -1,12 +1,15 @@
 #!./bin/python3
 #!./bin/python3
 
 
+import time
+import threading
+
 from display import Display
 from display import Display
 from typing import Dict
 from typing import Dict
 from pymplb import MPlayer
 from pymplb import MPlayer
 
 
 class Player(Display):
 class Player(Display):
     '''
     '''
-    Player object
+    Player object which interacts with mplayer.
     '''
     '''
     def __init__(self, config, pydsp):
     def __init__(self, config, pydsp):
         super().__init__({
         super().__init__({
@@ -16,21 +19,95 @@ class Player(Display):
             "fontsize" : config.get("fontsize"),
             "fontsize" : config.get("fontsize"),
         }, pydsp)
         }, pydsp)
 
 
-        self._player = MPlayer()
+        self.playing = False # Flag if the player is playing
+        self._playingPlaylist = None # Current playing order
+
+        self._data = [] # Current playlist
+ 
+        self._player = MPlayer() # Reference to mplayer control object
+
+        threading.Thread(target=self._updateFromPlayer, daemon=True).start()
+
+    def _updateFromPlayer(self):
+        '''
+        Thread to update the player display from the state of mplayer.
+        '''
+        while True:
+
+            try:
+                time.sleep(0.5)
+
+                if self.playing:
+                    player_playing = self._player.p_path
+
+                    # Update playlist display if the current song has changed
+                    # and the item can be found in the current playlist
+
+                    if player_playing != self._current_item.get("path"):
+                        
+                        for i, item in enumerate(self._data):
+                            if item.get("path") == player_playing:
+                                self._selection_pointer = i
+                                self._current_item = item
+                                self.update()
+
+                    # Start the playlist again if the player ran out of items
+
+                    if player_playing == "(null)":
+                        for item in self._playingPlaylist:
+                            self._player.loadfile(item["path"], 1)
 
 
-    def setData(self, data: Dict):
+            except Exception as e:
+                print("Error update from player:", e)
+
+
+    def setPlaylist(self, data: Dict):
+        '''
+        Set the playlist for this player.
+        '''
+        self._selection_pointer = 0
         self._data = data
         self._data = data
 
 
-    def loadPlaylist(self) -> Dict:
+    def getPlaylist(self) -> Dict:
+        '''
+        Return the playlist for this display.
+        '''
         return self._data
         return self._data
     
     
     def togglePlay(self):
     def togglePlay(self):
+        '''
+        Toggle playing the selected item.
+        '''
         if not self._current_item:
         if not self._current_item:
             return
             return
         
         
         if self._player.p_path == self._current_item["path"]:
         if self._player.p_path == self._current_item["path"]:
             print("stop")
             print("stop")
+            self.playing = False
             self._player.stop()
             self._player.stop()
         else:
         else:
-            print("Playing:", self._current_item)
-            self._player.loadfile(self._current_item["path"])
+            ci = self._current_item["path"]
+
+            self._playingPlaylist = []
+
+            add = 0
+            for item in self._data:
+
+                if item == self._current_item:
+                    self.playing = True
+                    self._player.loadfile(item["path"])
+                    self._playingPlaylist.append(item)
+                    add = 1
+
+                elif add == 1:
+                    self._player.loadfile(item["path"], 1)
+                    self._playingPlaylist.append(item)
+
+            for item in self._data:
+
+                if item != self._current_item:
+                    self._player.loadfile(item["path"], 1)
+                    self._playingPlaylist.append(item)
+
+                else:
+                    break

+ 1 - 0
musikautomat/requirements.txt

@@ -3,5 +3,6 @@ mypy-extensions==0.4.3
 pkg-resources==0.0.0
 pkg-resources==0.0.0
 pygame==2.0.0.dev6
 pygame==2.0.0.dev6
 PyYAML==5.1.2
 PyYAML==5.1.2
+tornado==6.0.3
 typed-ast==1.4.0
 typed-ast==1.4.0
 typing-extensions==3.7.4.1
 typing-extensions==3.7.4.1

+ 1 - 0
musikautomat/web/index.html

@@ -0,0 +1 @@
+<HTML>foo</HTML>