1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-19 19:01:03 +00:00
supysonic/supysonic/jukebox.py
Alban Féron 81d141e540
pyupgrade
2020-11-22 16:12:14 +01:00

161 lines
4.2 KiB
Python

# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2019 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
import logging
import shlex
import time
from datetime import datetime, timedelta
from pony.orm import db_session, ObjectNotFound
from random import shuffle
from subprocess import Popen, DEVNULL
from threading import Thread, Event, RLock
from .db import Track
logger = logging.getLogger(__name__)
class Jukebox:
def __init__(self, cmd):
self.__cmd = shlex.split(cmd)
self.__playlist = []
self.__index = 0
self.__offset = 0
self.__start = None
self.__thread = None
self.__lock = RLock()
self.__skip = Event()
self.__stop = Event()
playing = property(
lambda self: self.__thread is not None and self.__thread.is_alive()
)
index = property(lambda self: self.__index)
gain = property(lambda self: 1.0)
playlist = property(lambda self: list(self.__playlist))
@property
def position(self):
if self.__start is None:
return 0
return int((datetime.utcnow() - self.__start).total_seconds())
def set(self, *tracks):
self.clear()
self.add(*tracks)
def start(self):
if self.playing or not self.__playlist:
return
self.__skip.clear()
self.__stop.clear()
self.__offset = 0
self.__thread = Thread(target=self.__play_thread)
self.__thread.start()
def stop(self):
if not self.playing:
return
self.__stop.set()
def skip(self, index, offset):
if index < 0 or index >= len(self.__playlist):
raise IndexError()
if offset < 0:
raise ValueError()
with self.__lock:
self.__index = index
self.__offset = offset
self.__start = datetime.utcnow() - timedelta(seconds=offset)
self.__skip.set()
self.start()
def add(self, *tracks):
with self.__lock:
with db_session:
for t in tracks:
try:
self.__playlist.append(Track[t].path)
except ObjectNotFound:
pass
def clear(self):
with self.__lock:
self.__playlist.clear()
self.__index = 0
self.__offset = 0
def remove(self, index):
try:
with self.__lock:
self.__playlist.pop(index)
if index < self.__index:
self.__index -= 1
except IndexError:
pass
def shuffle(self):
with self.__lock:
shuffle(self.__playlist)
def setgain(self, gain):
pass
def terminate(self):
self.__stop.set()
if self.__thread is not None:
self.__thread.join()
def __play_thread(self):
proc = None
while not self.__stop.is_set():
if self.__skip.is_set():
proc.terminate()
proc.wait()
proc = None
self.__skip.clear()
if proc is None:
with self.__lock:
proc = self.__play_file()
elif proc.poll() is not None:
with self.__lock:
self.__start = None
self.__index += 1
if self.__index >= len(self.__playlist):
break
proc = self.__play_file()
time.sleep(0.1)
proc.terminate()
proc.wait()
self.__start = None
def __play_file(self):
path = self.__playlist[self.__index]
args = [
a.replace("%path", path).replace("%offset", str(self.__offset))
for a in self.__cmd
]
self.__start = datetime.utcnow() - timedelta(seconds=self.__offset)
self.__offset = 0
logger.debug("Start playing with command %s", args)
try:
return Popen(args, stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL)
except Exception:
logger.exception("Failed to run play command")
return None