change album cover layout and add improve player syncing when connection is dropped
This commit is contained in:
84
app.py
84
app.py
@@ -120,7 +120,7 @@ def load_album_assets():
|
|||||||
|
|
||||||
def setup_3d_environment(render_width, render_height):
|
def setup_3d_environment(render_width, render_height):
|
||||||
camera = pr.Camera3D()
|
camera = pr.Camera3D()
|
||||||
camera.position = pr.Vector3(0.0, 0.0, 4.0) # Moved back slightly to fit the new models
|
camera.position = pr.Vector3(0.0, -0.35, 4.0) # Moved back slightly to fit the new models
|
||||||
camera.target = pr.Vector3(0.0, 0.0, 0.0)
|
camera.target = pr.Vector3(0.0, 0.0, 0.0)
|
||||||
camera.up = pr.Vector3(0.0, 1.0, 0.0)
|
camera.up = pr.Vector3(0.0, 1.0, 0.0)
|
||||||
camera.fovy = 45.0
|
camera.fovy = 45.0
|
||||||
@@ -141,64 +141,27 @@ def draw_3d_cover_flow(camera, model):
|
|||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
# Draw model at (0,0,0) with 1.0 scale
|
# Draw model at (0,0,0) with 1.0 scale
|
||||||
pr.rl_push_matrix()
|
pr.rl_push_matrix()
|
||||||
pr.rl_translatef(0.0, 0.0, 1.5) # Spaced out slightly more
|
pr.rl_translatef(0.0, -0.0, 1.5) # Spaced out slightly more
|
||||||
pr.rl_rotatef(0.0, 0.0, 1.0, 0.0) # Sharper angle
|
pr.rl_rotatef(5.0, 1.0, 0.0, 0.0) # Sharper angle
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
||||||
pr.rl_pop_matrix()
|
pr.rl_pop_matrix()
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# 3. PREVIOUS ALBUM (Far Far Left)
|
|
||||||
# --------------------------------------------------------
|
|
||||||
pr.rl_push_matrix()
|
|
||||||
pr.rl_translatef(-3.5, 0.0, 0.0) # Spaced out slightly more
|
|
||||||
pr.rl_rotatef(90.0, 0.0, 1.0, 0.0) # Sharper angle
|
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) # Slightly darkened
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
for i in range(-5, 0):
|
||||||
# 3. PREVIOUS ALBUM (Far Left)
|
pr.rl_push_matrix()
|
||||||
# --------------------------------------------------------
|
pr.rl_translatef(-1.5+0.15*i, 0.0, 0.5) # Added slight Z offset for depth
|
||||||
pr.rl_push_matrix()
|
pr.rl_rotatef(50.0, 0.0, 1.0, 0.0)
|
||||||
pr.rl_translatef(-2.5, 0.0, 0.0) # Spaced out slightly more
|
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
||||||
pr.rl_rotatef(90.0, 0.0, 1.0, 0.0) # Sharper angle
|
pr.rl_pop_matrix()
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) # Slightly darkened
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# 3. PREVIOUS ALBUM (Near Left)
|
|
||||||
# --------------------------------------------------------
|
|
||||||
pr.rl_push_matrix()
|
|
||||||
pr.rl_translatef(-1.5, 0.0, 0.5) # Added slight Z offset for depth
|
|
||||||
pr.rl_rotatef(65.0, 0.0, 1.0, 0.0)
|
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# 4. NEXT ALBUM (Near Right)
|
|
||||||
# --------------------------------------------------------
|
|
||||||
pr.rl_push_matrix()
|
|
||||||
pr.rl_translatef(1.5, 0.0, 0.5)
|
|
||||||
pr.rl_rotatef(-65.0, 0.0, 1.0, 0.0)
|
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
for i in range(1,6):
|
||||||
# 4. NEXT ALBUM (Far Right)
|
pr.rl_push_matrix()
|
||||||
# --------------------------------------------------------
|
pr.rl_translatef(1.5+0.15*i, 0.0, 0.5)
|
||||||
pr.rl_push_matrix()
|
pr.rl_rotatef(-50.0, 0.0, 1.0, 0.0)
|
||||||
pr.rl_translatef(2.5, 0.0, 0.0)
|
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE)
|
||||||
pr.rl_rotatef(-90.0, 0.0, 1.0, 0.0)
|
pr.rl_pop_matrix()
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY)
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# 4. NEXT ALBUM (Far Far Right)
|
|
||||||
# --------------------------------------------------------
|
|
||||||
pr.rl_push_matrix()
|
|
||||||
pr.rl_translatef(3.5, 0.0, 0.0)
|
|
||||||
pr.rl_rotatef(-90.0, 0.0, 1.0, 0.0)
|
|
||||||
pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY)
|
|
||||||
pr.rl_pop_matrix()
|
|
||||||
|
|
||||||
pr.end_mode_3d()
|
pr.end_mode_3d()
|
||||||
|
|
||||||
@@ -213,8 +176,24 @@ pr.set_target_fps(TARGET_FPS)
|
|||||||
player = FFQueuePlayer()
|
player = FFQueuePlayer()
|
||||||
|
|
||||||
print("add queue")
|
print("add queue")
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("dab6efb24bb2372794d2b4fb53a12376")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("58822c0fc47ec63ba798ba4f04ea3cf3")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("6382005f9dbae8d187d80a5cdca3e7a6")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("a5d2453e07a4998ea20e957c44f90be6")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("398d481a7b85287ad200578b5ab997b0")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("f9f32ca67be7f83139cee3c66e1e4965")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("2f651e103b1fd22ea2f202d6f3398b36")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("164b95968ab1a725fff060fa8c351cc8")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
|
||||||
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("38a6c21561f54d284a6acad89a3ea8b0")["Id"], server["AccessToken"], server["UserId"]))
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("38a6c21561f54d284a6acad89a3ea8b0")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("631aeddb0557fef65f49463abb20ad7f")["Id"], server["AccessToken"], server["UserId"]))
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("631aeddb0557fef65f49463abb20ad7f")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("3d611c8664c5b2072edbf46da2a76c89")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("66559c40d5904944a3f97198d0297894")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("84b75eeb5c8e862d002bae05d2671b1b")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("7ef66992426093252696e1d8666a22e4")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("f37982227942d3df031381e653ec5790")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
|
player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("0e8fc5fcf119de0439f5a15a4f255c5c")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
player.add_to_queue('music/pink floyd/dark side of the moon/01 Speak to Me.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("99067e877d91be1a66eb5a7ff2f4128f")["Id"], server["AccessToken"], server["UserId"]))
|
player.add_to_queue('music/pink floyd/dark side of the moon/01 Speak to Me.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("99067e877d91be1a66eb5a7ff2f4128f")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
player.add_to_queue('music/pink floyd/dark side of the moon/02 Breathe (In the Air).flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("916eda422f48efd8705f29e0600a3e60")["Id"], server["AccessToken"], server["UserId"]))
|
player.add_to_queue('music/pink floyd/dark side of the moon/02 Breathe (In the Air).flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("916eda422f48efd8705f29e0600a3e60")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
player.add_to_queue('music/pink floyd/dark side of the moon/03 On the Run.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("5e1067d59ed98979ad12a58548b27b83")["Id"], server["AccessToken"], server["UserId"]))
|
player.add_to_queue('music/pink floyd/dark side of the moon/03 On the Run.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("5e1067d59ed98979ad12a58548b27b83")["Id"], server["AccessToken"], server["UserId"]))
|
||||||
@@ -226,7 +205,6 @@ player.add_to_queue('music/pink floyd/dark side of the moon/08 Any Colour You Li
|
|||||||
player.add_to_queue('music/pink floyd/dark side of the moon/09 Brain Damage.flac')
|
player.add_to_queue('music/pink floyd/dark side of the moon/09 Brain Damage.flac')
|
||||||
player.add_to_queue('music/pink floyd/dark side of the moon/10 Eclipse.flac')
|
player.add_to_queue('music/pink floyd/dark side of the moon/10 Eclipse.flac')
|
||||||
print("add queue done")
|
print("add queue done")
|
||||||
player.play()
|
|
||||||
|
|
||||||
# Initial setup
|
# Initial setup
|
||||||
render_rect = get_3d_render_area(state["screen_width"], state["screen_height"])
|
render_rect = get_3d_render_area(state["screen_width"], state["screen_height"])
|
||||||
|
|||||||
36
player.py
36
player.py
@@ -16,6 +16,7 @@ import threading
|
|||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
import io
|
import io
|
||||||
|
import fcntl
|
||||||
|
|
||||||
os.makedirs("logs", exist_ok=True)
|
os.makedirs("logs", exist_ok=True)
|
||||||
|
|
||||||
@@ -59,9 +60,10 @@ class FFQueuePlayer:
|
|||||||
|
|
||||||
def _open_ffmpeg(self, url, seek=0):
|
def _open_ffmpeg(self, url, seek=0):
|
||||||
self.song+=1
|
self.song+=1
|
||||||
return subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[
|
[
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
|
# "-re",
|
||||||
"-ss", str(seek),
|
"-ss", str(seek),
|
||||||
"-i", url,
|
"-i", url,
|
||||||
"-f", "s16le",
|
"-f", "s16le",
|
||||||
@@ -74,6 +76,13 @@ class FFQueuePlayer:
|
|||||||
stderr=open('logs/'+str(self.song)+".txt", "wb")
|
stderr=open('logs/'+str(self.song)+".txt", "wb")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- make stdout non-blocking ---
|
||||||
|
fd = proc.stdout.fileno()
|
||||||
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
return proc
|
||||||
|
|
||||||
def seek(self, pos):
|
def seek(self, pos):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
pos = min(max(0,pos), self.playback_info_to_duration(self.playback_info))
|
pos = min(max(0,pos), self.playback_info_to_duration(self.playback_info))
|
||||||
@@ -173,18 +182,25 @@ class FFQueuePlayer:
|
|||||||
elif self.next_preload_state == 0:
|
elif self.next_preload_state == 0:
|
||||||
self.preload_next_threaded()
|
self.preload_next_threaded()
|
||||||
else:
|
else:
|
||||||
data = self.proc.stdout.read(needed) or b''
|
try:
|
||||||
|
data = self.proc.stdout.read(needed) or b''
|
||||||
|
except BlockingIOError:
|
||||||
|
pass
|
||||||
self.position += len(data) / (self.samplerate * self.channels * 2)
|
self.position += len(data) / (self.samplerate * self.channels * 2)
|
||||||
if self.position >= self.playback_info_to_duration(self.playback_info)-10:
|
if self.position >= self.playback_info_to_duration(self.playback_info)-10:
|
||||||
self.preload_next_threaded()
|
self.preload_next_threaded()
|
||||||
if self.proc.poll() is not None and len(data)<needed:
|
if self.proc.poll() is not None and len(data)<needed:
|
||||||
self._start_next()
|
if round(self.position, 2) >= self.playback_info_to_duration(self.playback_info)-0.1:
|
||||||
if self.proc is not None and self.proc.poll() is None:
|
self._start_next()
|
||||||
new_data = self.proc.stdout.read(needed-len(data)) or b''
|
if self.proc is not None and self.proc.poll() is None:
|
||||||
self.position += len(new_data) / (self.samplerate * self.channels * 2)
|
try:
|
||||||
data += new_data
|
new_data = self.proc.stdout.read(needed-len(data)) or b''
|
||||||
|
except BlockingIOError:
|
||||||
|
new_data = b''
|
||||||
|
self.position += len(new_data) / (self.samplerate * self.channels * 2)
|
||||||
|
data += new_data
|
||||||
else:
|
else:
|
||||||
print("bruh")
|
self.proc = self._open_ffmpeg(self.current_file, self.position)
|
||||||
|
|
||||||
outdata[:len(data)] = data
|
outdata[:len(data)] = data
|
||||||
|
|
||||||
@@ -204,7 +220,7 @@ def build_jellyfin_audio_url(
|
|||||||
"""
|
"""
|
||||||
Build a Jellyfin audio stream URL using urllib.parse.
|
Build a Jellyfin audio stream URL using urllib.parse.
|
||||||
"""
|
"""
|
||||||
path = f"/Items/{item_id}/Download"
|
path = f"/Audio/{item_id}/universal"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"UserId": user_id,
|
"UserId": user_id,
|
||||||
@@ -226,7 +242,7 @@ def build_jellyfin_audio_url(
|
|||||||
client = JellyfinClient()
|
client = JellyfinClient()
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
client.config.app('FinPod', '0.0.1', 'FinPod prototype', 'FinPod_prototype_1')
|
client.config.app('UgPod', '0.0.1', 'UgPod prototype', 'UgPod_prototype_1')
|
||||||
client.config.data["auth.ssl"] = True
|
client.config.data["auth.ssl"] = True
|
||||||
|
|
||||||
client.auth.connect_to_address(os.getenv("host"))
|
client.auth.connect_to_address(os.getenv("host"))
|
||||||
|
|||||||
Reference in New Issue
Block a user