streamer: integrate, fix, ui change

This commit is contained in:
bkfox 2024-04-28 18:59:33 +02:00
parent a64c850efa
commit 87692c860b
8 changed files with 79 additions and 76 deletions

0
README.md Executable file → Normal file
View File

View File

@ -24,6 +24,7 @@ Usefull context:
{ {
"imports": { "imports": {
"vue": "{{vue_url}}" "vue": "{{vue_url}}"
{% block assets-import-map %}{% endblock %}
} }
} }
</script> </script>

View File

@ -70,14 +70,14 @@ class Connector:
data = bytes("".join([str(d) for d in data]) + "\n", encoding="utf-8") data = bytes("".join([str(d) for d in data]) + "\n", encoding="utf-8")
try: try:
self.socket.sendall(data) self.socket.sendall(data)
data = "" resp = ""
while not response_re.search(data): while not response_re.search(resp):
data += self.socket.recv(1024).decode("utf-8") resp += self.socket.recv(1024).decode("utf-8")
if data: if resp:
data = response_re.sub(r"\1", data).strip() resp = response_re.sub(r"\1", resp).strip()
data = self.parse(data) if parse else self.parse_json(data) if parse_json else data resp = self.parse(resp) if parse else self.parse_json(resp) if parse_json else resp
return data return resp
except Exception: except Exception:
self.close() self.close()
if try_count > 0: if try_count > 0:

View File

@ -131,7 +131,7 @@ class QueueSource(Source):
def push(self, *paths): def push(self, *paths):
"""Add the provided paths to source's play queue.""" """Add the provided paths to source's play queue."""
for path in paths: for path in paths:
self.controller.send(f"{self.id}_queue.push {path}") print(self.controller.send(f"{self.id}_queue.push {path}"))
def fetch(self): def fetch(self):
super().fetch() super().fetch()

View File

@ -19,7 +19,7 @@ end
{# Transition to live sources #} {# Transition to live sources #}
def to_live(stream, live) def to_live(stream, live)
stream = fade.final(duration=2., type='log', stream) stream = fade.out(duration=2., type='log', stream)
live = fade.initial(duration=2., type='log', live) live = fade.initial(duration=2., type='log', live)
add(normalize=false, [stream,live]) add(normalize=false, [stream,live])
end end
@ -31,12 +31,12 @@ def to_stream(live, stream)
end end
{# Skip command #} {# Skip command #}
def add_skip_command(s) = def add_skip_command(id, s) =
def skip(_) = def skip(_) =
source.skip(s) source.skip(s)
"Done!" "Done!"
end end
server.register(namespace="#{source.id(s)}", server.register(namespace=id,
usage="skip", usage="skip",
description="Skip the current song.", description="Skip the current song.",
"skip",skip) "skip",skip)
@ -58,9 +58,8 @@ def interactive (id, s) =
usage="remaining", usage="remaining",
"remaining", fun (_) -> begin json.stringify(source.remaining(s)) end) "remaining", fun (_) -> begin json.stringify(source.remaining(s)) end)
add_skip_command(s) add_skip_command(id, s)
{# metadata: create an interactive variable as "{id}_meta" #}
s_meta = interactive.string("#{id}_meta", "") s_meta = interactive.string("#{id}_meta", "")
s = source.on_metadata(s, fun(meta) -> s_meta.set(json.stringify(meta))) s = source.on_metadata(s, fun(meta) -> s_meta.set(json.stringify(meta)))
@ -82,10 +81,7 @@ set("server.socket", true)
set("server.socket.path", "{{ streamer.socket_path }}") set("server.socket.path", "{{ streamer.socket_path }}")
set("log.file.path", "{{ log_file }}") set("log.file.path", "{{ log_file }}")
{% endblock %} {% endblock %}
{% block config_extras %}{% endblock %}
{% block config_extras %}
{% endblock %}
{% block sources %} {% block sources %}
{% with source=streamer.dealer %} {% with source=streamer.dealer %}
@ -94,7 +90,6 @@ live = audio_to_stereo(interactive('{{ source.id }}',
)) ))
{% endwith %} {% endwith %}
streams = rotate(id="streams", [ streams = rotate(id="streams", [
{% for source in streamer.sources %} {% for source in streamer.sources %}
{% if source != streamer.dealer %} {% if source != streamer.dealer %}

View File

@ -1,8 +1,9 @@
{% comment %}List item for a source.{% endcomment %} {% comment %}List item for a source.{% endcomment %}
{% load i18n %} {% load i18n %}
<section class="box"><div class="columns is-desktop"> <section class="box"><div class="flex-row gap-3">
<div class="column">
<div class="flex-grow-1">
<h5 class='title is-5' :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}"> <h5 class='title is-5' :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
<span> <span>
<span v-if="source.isPlaying" class="fas fa-play"></span> <span v-if="source.isPlaying" class="fas fa-play"></span>
@ -21,28 +22,70 @@
</a> </a>
</h5> </h5>
<table class="table bg-transparent">
<tbody>
<tr><th class="has-text-right ws-nowrap">
{% translate "Status" %}
</th>
<td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
<span v-if="source.isPlaying" class="fas fa-play"></span>
<span v-else-if="source.data.status" class="fas fa-pause"></span>
[[ source.data.status_verbose || "&mdash;" ]]
</td>
</tr>
<tr v-if="source.data.air_time">
<th class="has-text-right ws-nowrap">
{% translate "Air time" %}
</th><td>
<span class="far fa-clock"></span>
<time :datetime="source.date">
[[ source.data.air_time.toLocaleDateString() ]],
[[ source.data.air_time.toLocaleTimeString() ]]
</time>
</td>
<tr v-if="source.remaining">
<th class="has-text-right ws-nowrap">
{% translate "Time left" %}
</th><td>
<span class="far fa-hourglass"></span>
[[ source.remainingString ]]
</td>
</tr>
<tr v-if="source.data.uri">
<th class="has-text-right ws-nowrap">
{% translate "Data source" %}
</th><td>
<span class="far fa-play-circle"></span>
<template v-if="source.data.uri.length > 64">...</template>[[ (source.data.uri && source.data.uri.slice(-64)) || '&mdash;' ]]
</td>
</tr>
</tbody>
</table>
</div>
<div>
<div> <div>
<button class="button" @click="source.sync()" <button class="button smaller mr-2 mb-2" @click="source.restart()"
title="{% translate "Synchronize source with Liquidsoap" %}">
<span class="icon is-small">
<span class="fas fa-sync"></span>
</span>
<span>{% translate "Synchronise" %}</span>
</button>
<button class="button" @click="source.restart()"
title="{% translate "Restart current track" %}"> title="{% translate "Restart current track" %}">
<span class="icon is-small"> <span class="icon is-small">
<span class="fas fa-step-backward"></span> <span class="fas fa-step-backward"></span>
</span> </span>
<span>{% translate "Restart" %}</span> <span>{% translate "Restart" %}</span>
</button> </button>
<button class="button" @click="source.skip()" <button class="button smaller mr-2 mb-2" @click="source.skip()"
title="{% translate "Skip current file" %}"> title="{% translate "Skip current file" %}">
<span>{% translate "Skip" %}</span> <span>{% translate "Skip" %}</span>
<span class="icon is-small"> <span class="icon is-small">
<span class="fas fa-step-forward"></span> <span class="fas fa-step-forward"></span>
</span> </span>
</button> </button>
<button class="button smaller mr-2 mb-2" @click="source.sync()"
title="{% translate "Synchronize source with Liquidsoap" %}">
<span class="icon is-small">
<span class="fas fa-sync"></span>
</span>
<span>{% translate "Synchronise" %}</span>
</button>
</div> </div>
<div v-if="source.isQueue"> <div v-if="source.isQueue">
@ -89,46 +132,4 @@
</div> </div>
</div> </div>
<div class="column is-two-fifths">
<h6 class="subtitle is-6 is-marginless">Metadata</h6>
<table class="table bg-transparent">
<tbody>
<tr><th class="has-text-right ws-nowrap">
{% translate "Status" %}
</th>
<td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
<span v-if="source.isPlaying" class="fas fa-play"></span>
<span v-else-if="source.data.status" class="fas fa-pause"></span>
[[ source.data.status_verbose || "&mdash;" ]]
</td>
</tr>
<tr v-if="source.data.air_time">
<th class="has-text-right ws-nowrap">
{% translate "Air time" %}
</th><td>
<span class="far fa-clock"></span>
<time :datetime="source.date">
[[ source.data.air_time.toLocaleDateString() ]],
[[ source.data.air_time.toLocaleTimeString() ]]
</time>
</td>
<tr v-if="source.remaining">
<th class="has-text-right ws-nowrap">
{% translate "Time left" %}
</th><td>
<span class="far fa-hourglass"></span>
[[ source.remainingString ]]
</td>
</tr>
<tr v-if="source.data.uri">
<th class="has-text-right ws-nowrap">
{% translate "Data source" %}
</th><td>
<span class="far fa-play-circle"></span>
<template v-if="source.data.uri.length > 64">...</template>[[ (source.data.uri && source.data.uri.slice(-64)) || '&mdash;' ]]
</td>
</tr>
</tbody>
</table>
</div>
</div></section> </div></section>

4
aircox_streamer/urls.py Normal file → Executable file
View File

@ -9,10 +9,10 @@ from . import views, viewsets
__all__ = ("api", "urls") __all__ = ("api", "urls")
prefix = "(?P<station_pk>[0-9]+)/" prefix = "<int:station_pk>/"
router = DefaultRouter() router = DefaultRouter(use_regex_path=False)
router.register(prefix + "playlist", viewsets.PlaylistSourceViewSet, basename="streamer-playlist") router.register(prefix + "playlist", viewsets.PlaylistSourceViewSet, basename="streamer-playlist")
router.register(prefix + "queue", viewsets.QueueSourceViewSet, basename="streamer-queue") router.register(prefix + "queue", viewsets.QueueSourceViewSet, basename="streamer-queue")
router.register("streamer", viewsets.StreamerViewSet, basename="streamer") router.register("streamer", viewsets.StreamerViewSet, basename="streamer")

12
aircox_streamer/viewsets.py Normal file → Executable file
View File

@ -46,7 +46,7 @@ class ControllerViewSet(viewsets.ViewSet):
if station_pk is None: if station_pk is None:
return None return None
if station_pk not in self.streamers: if station_pk not in self.streamers:
raise Http404("station not found") raise Http404(f"station not found: {station_pk}")
return self.streamers[station_pk] return self.streamers[station_pk]
def get_serializer(self, **kwargs): def get_serializer(self, **kwargs):
@ -60,7 +60,8 @@ class ControllerViewSet(viewsets.ViewSet):
return serializer.data return serializer.data
def dispatch(self, request, *args, station_pk=None, **kwargs): def dispatch(self, request, *args, station_pk=None, **kwargs):
self.streamer = self.get_streamer(station_pk) if not self.streamer:
self.streamer = self.get_streamer(station_pk)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -80,6 +81,9 @@ class StreamerViewSet(ControllerViewSet):
def dispatch(self, request, *args, pk=None, **kwargs): def dispatch(self, request, *args, pk=None, **kwargs):
if pk is not None: if pk is not None:
kwargs.setdefault("station_pk", pk) kwargs.setdefault("station_pk", pk)
if pk := kwargs.get("station_pk"):
kwargs["station_pk"] = int(pk)
self.streamer = self.get_streamer(**kwargs) self.streamer = self.get_streamer(**kwargs)
self.object = self.streamer self.object = self.streamer
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -88,6 +92,8 @@ class StreamerViewSet(ControllerViewSet):
class SourceViewSet(ControllerViewSet): class SourceViewSet(ControllerViewSet):
serializer_class = SourceSerializer serializer_class = SourceSerializer
model = controllers.Source model = controllers.Source
lookup_field = "pk"
lookup_value_converter = "str"
def get_sources(self): def get_sources(self):
return (s for s in self.streamer.sources if isinstance(s, self.model)) return (s for s in self.streamer.sources if isinstance(s, self.model))
@ -139,7 +145,7 @@ class QueueSourceViewSet(SourceViewSet):
model = controllers.QueueSource model = controllers.QueueSource
def get_sound_queryset(self, request): def get_sound_queryset(self, request):
return Sound.objects.station(request.station).broadcast() return Sound.objects.station(request.station)
@action(detail=True, methods=["POST"]) @action(detail=True, methods=["POST"])
def push(self, request, pk): def push(self, request, pk):