forked from rc/aircox
streamer as separate application; working streamer monitor interface
This commit is contained in:
@ -3,3 +3,5 @@ from django.apps import AppConfig
|
||||
|
||||
class AircoxStreamerConfig(AppConfig):
|
||||
name = 'aircox_streamer'
|
||||
|
||||
|
||||
|
@ -43,6 +43,8 @@ class BaseMetadata:
|
||||
""" Request uri """
|
||||
status = None
|
||||
""" Current playing status """
|
||||
request_status = None
|
||||
""" Requests' status """
|
||||
air_time = None
|
||||
""" Launch datetime """
|
||||
|
||||
@ -58,10 +60,25 @@ class BaseMetadata:
|
||||
return self.status == 'playing'
|
||||
|
||||
def fetch(self):
|
||||
data = self.controller.set('request.metadata ', self.rid, parse=True)
|
||||
data = self.controller.send('request.metadata ', self.rid, parse=True)
|
||||
if data:
|
||||
self.validate(data)
|
||||
|
||||
def validate_status(self, status):
|
||||
on_air = self.controller.source
|
||||
if on_air and status == 'playing' and (on_air == self or
|
||||
on_air.rid == self.rid):
|
||||
return 'playing'
|
||||
elif status == 'playing':
|
||||
return 'paused'
|
||||
else:
|
||||
return 'stopped'
|
||||
|
||||
def validate_air_time(self, air_time):
|
||||
if air_time:
|
||||
air_time = tz.datetime.strptime(air_time, '%Y/%m/%d %H:%M:%S')
|
||||
return local_tz.localize(air_time)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate provided data and set as attribute (must already be
|
||||
@ -72,12 +89,9 @@ class BaseMetadata:
|
||||
setattr(self, key, value)
|
||||
self.uri = data.get('initial_uri')
|
||||
|
||||
air_time = data.get('on_air')
|
||||
if air_time:
|
||||
air_time = tz.datetime.strptime(air_time, '%Y/%m/%d %H:%M:%S')
|
||||
self.air_time = local_tz.localize(air_time)
|
||||
else:
|
||||
self.air_time = None
|
||||
self.air_time = self.validate_air_time(data.get('on_air'))
|
||||
self.status = self.validate_status(data.get('status'))
|
||||
self.request_status = data.get('status')
|
||||
|
||||
|
||||
class Request(BaseMetadata):
|
||||
@ -142,6 +156,14 @@ class Streamer:
|
||||
logger.debug('process died with return code %s' % returncode)
|
||||
return False
|
||||
|
||||
@property
|
||||
def playlists(self):
|
||||
return (s for s in self.sources if isinstance(s, PlaylistSource))
|
||||
|
||||
@property
|
||||
def queues(self):
|
||||
return (s for s in self.sources if isinstance(s, QueueSource))
|
||||
|
||||
# Sources and config ###############################################
|
||||
def send(self, *args, **kwargs):
|
||||
return self.connector.send(*args, **kwargs) or ''
|
||||
@ -180,12 +202,11 @@ class Streamer:
|
||||
source.fetch()
|
||||
|
||||
# request.on_air is not ordered: we need to do it manually
|
||||
if self.dealer.is_playing:
|
||||
self.source = self.dealer
|
||||
return
|
||||
|
||||
self.source = next((source for source in self.sources
|
||||
if source.is_playing), None)
|
||||
self.source = next(iter(sorted(
|
||||
(source for source in self.sources
|
||||
if source.request_status == 'playing' and source.air_time),
|
||||
key=lambda o: o.air_time, reverse=True
|
||||
)), None)
|
||||
|
||||
# Process ##########################################################
|
||||
def get_process_args(self):
|
||||
@ -241,15 +262,12 @@ class Source(BaseMetadata):
|
||||
""" source id """
|
||||
remaining = 0.0
|
||||
""" remaining time """
|
||||
status = 'stopped'
|
||||
|
||||
@property
|
||||
def station(self):
|
||||
return self.controller.station
|
||||
|
||||
# @property
|
||||
# def is_on_air(self):
|
||||
# return self.rid is not None and self.rid in self.controller.on_air
|
||||
|
||||
def __init__(self, controller=None, id=None, *args, **kwargs):
|
||||
super().__init__(controller, *args, **kwargs)
|
||||
self.id = id
|
||||
@ -258,9 +276,12 @@ class Source(BaseMetadata):
|
||||
""" Synchronize what should be synchronized """
|
||||
|
||||
def fetch(self):
|
||||
data = self.controller.send(self.id, '.remaining')
|
||||
if data:
|
||||
self.remaining = float(data)
|
||||
try:
|
||||
data = self.controller.send(self.id, '.remaining')
|
||||
if data:
|
||||
self.remaining = float(data)
|
||||
except ValueError:
|
||||
self.remaining = None
|
||||
|
||||
data = self.controller.send(self.id, '.get', parse=True)
|
||||
if data:
|
||||
@ -332,12 +353,9 @@ class PlaylistSource(Source):
|
||||
class QueueSource(Source):
|
||||
queue = None
|
||||
""" Source's queue (excluded on_air request) """
|
||||
as_requests = False
|
||||
""" If True, queue is a list of Request """
|
||||
|
||||
def __init__(self, *args, queue_metadata=False, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.queue_metadata = queue_metadata
|
||||
|
||||
def push(self, *paths):
|
||||
""" Add the provided paths to source's play queue """
|
||||
@ -346,13 +364,19 @@ class QueueSource(Source):
|
||||
|
||||
def fetch(self):
|
||||
super().fetch()
|
||||
queue = self.controller.send(self.id, '_queue.queue').split(' ')
|
||||
if not self.as_requests:
|
||||
self.queue = queue
|
||||
queue = self.controller.send(self.id, '_queue.queue').strip()
|
||||
if not queue:
|
||||
self.queue = []
|
||||
return
|
||||
|
||||
self.queue = [Request(self.controller, rid) for rid in queue]
|
||||
for request in self.queue:
|
||||
self.queue = queue.split(' ')
|
||||
|
||||
@property
|
||||
def requests(self):
|
||||
""" Queue as requests metadata """
|
||||
requests = [Request(self.controller, rid) for rid in self.queue]
|
||||
for request in requests:
|
||||
request.fetch()
|
||||
return requests
|
||||
|
||||
|
@ -24,7 +24,7 @@ from django.utils import timezone as tz
|
||||
from aircox.models import Station, Episode, Diffusion, Track, Sound, Log
|
||||
from aircox.utils import date_range
|
||||
|
||||
from aircox_streamer.liquidsoap import Streamer, PlaylistSource
|
||||
from aircox_streamer.controllers import Streamer
|
||||
|
||||
|
||||
# force using UTC
|
||||
@ -246,9 +246,8 @@ class Monitor:
|
||||
|
||||
self.sync_next = now + tz.timedelta(minutes=self.sync_timeout)
|
||||
|
||||
for source in self.streamer.sources:
|
||||
if isinstance(source, PlaylistSource):
|
||||
source.sync()
|
||||
for source in self.streamer.playlists:
|
||||
source.sync()
|
||||
|
||||
|
||||
class Command (BaseCommand):
|
||||
@ -291,10 +290,8 @@ class Command (BaseCommand):
|
||||
)
|
||||
# TODO: sync-timeout, cancel-timeout
|
||||
|
||||
def handle(self, *args,
|
||||
config=None, run=None, monitor=None,
|
||||
station=[], delay=1000, timeout=600,
|
||||
**options):
|
||||
def handle(self, *args, config=None, run=None, monitor=None, station=[],
|
||||
delay=1000, timeout=600, **options):
|
||||
stations = Station.objects.filter(name__in=station) if station else \
|
||||
Station.objects.all()
|
||||
streamers = [Streamer(station) for station in stations]
|
||||
|
@ -1,24 +1,35 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from .controllers import QueueSource, PlaylistSource
|
||||
|
||||
|
||||
__all__ = ['RequestSerializer', 'StreamerSerializer', 'SourceSerializer',
|
||||
'PlaylistSerializer', 'QueueSourceSerializer']
|
||||
# TODO: use models' serializers
|
||||
|
||||
|
||||
class BaseMetadataSerializer(serializers.Serializer):
|
||||
class BaseSerializer(serializers.Serializer):
|
||||
url_ = serializers.SerializerMethodField('get_url')
|
||||
url_name = None
|
||||
|
||||
def get_url(self, obj, **kwargs):
|
||||
if not obj or not self.url_name:
|
||||
return
|
||||
kwargs.setdefault('pk', getattr(obj, 'id', None))
|
||||
return reverse(self.url_name, kwargs=kwargs)
|
||||
|
||||
|
||||
class BaseMetadataSerializer(BaseSerializer):
|
||||
rid = serializers.IntegerField()
|
||||
air_time = serializers.DateTimeField()
|
||||
uri = serializers.CharField()
|
||||
|
||||
|
||||
class RequestSerializer(serializers.Serializer):
|
||||
title = serializers.CharField()
|
||||
artist = serializers.CharField()
|
||||
|
||||
|
||||
class StreamerSerializer(serializers.Serializer):
|
||||
station = serializers.CharField(source='station.title')
|
||||
class RequestSerializer(BaseMetadataSerializer):
|
||||
title = serializers.CharField(required=False)
|
||||
artist = serializers.CharField(required=False)
|
||||
|
||||
|
||||
class SourceSerializer(BaseMetadataSerializer):
|
||||
@ -27,14 +38,34 @@ class SourceSerializer(BaseMetadataSerializer):
|
||||
rid = serializers.IntegerField()
|
||||
air_time = serializers.DateTimeField()
|
||||
status = serializers.CharField()
|
||||
remaining = serializers.FloatField()
|
||||
|
||||
def get_url(self, obj, **kwargs):
|
||||
kwargs['station_pk'] = obj.station.pk
|
||||
return super().get_url(obj, **kwargs)
|
||||
|
||||
|
||||
class PlaylistSerializer(SourceSerializer):
|
||||
program = serializers.CharField(source='program.title')
|
||||
playlist = serializers.ListField(child=serializers.CharField())
|
||||
|
||||
url_name = 'admin:api:streamer-playlist-detail'
|
||||
|
||||
class QueueSourceSerializer(SourceSerializer):
|
||||
queue = serializers.ListField(child=RequestSerializer())
|
||||
queue = serializers.ListField(child=RequestSerializer(), source='requests')
|
||||
|
||||
url_name = 'admin:api:streamer-queue-detail'
|
||||
|
||||
|
||||
class StreamerSerializer(BaseSerializer):
|
||||
id = serializers.IntegerField(source='station.pk')
|
||||
name = serializers.CharField(source='station.name')
|
||||
source = serializers.CharField(source='source.id', required=False)
|
||||
playlists = serializers.ListField(child=PlaylistSerializer())
|
||||
queues = serializers.ListField(child=QueueSourceSerializer())
|
||||
|
||||
url_name = 'admin:api:streamer-detail'
|
||||
|
||||
def get_url(self, obj, **kwargs):
|
||||
kwargs['pk'] = obj.station.pk
|
||||
return super().get_url(obj, **kwargs)
|
||||
|
||||
|
121
aircox_streamer/templates/aircox_streamer/source_item.html
Normal file
121
aircox_streamer/templates/aircox_streamer/source_item.html
Normal file
@ -0,0 +1,121 @@
|
||||
{% load i18n %}
|
||||
|
||||
<section class="box"><div class="columns is-desktop">
|
||||
<div class="column">
|
||||
<h5 class='title is-5' :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
|
||||
<span>
|
||||
<span v-if="source.isPlaying" class="fas fa-play"></span>
|
||||
<span v-else-if="source.isPaused" class="fas fa-pause"></span>
|
||||
</span>
|
||||
[[ source.id ]]
|
||||
|
||||
<small v-if="source.isPaused || source.isPlaying">(-[[ source.remainingString ]])</small>
|
||||
</h5>
|
||||
|
||||
<div>
|
||||
<button class="button" @click="source.sync()"
|
||||
title="{% trans "Synchronize source with Liquidsoap" %}">
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-sync"></span>
|
||||
</span>
|
||||
<span>{% trans "Synchronise" %}</span>
|
||||
</button>
|
||||
<button class="button" @click="source.restart()"
|
||||
title="{% trans "Restart current track" %}">
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-step-backward"></span>
|
||||
</span>
|
||||
<span>{% trans "Restart" %}</span>
|
||||
</button>
|
||||
<button class="button" @click="source.skip()"
|
||||
title="{% trans "Skip current file" %}">
|
||||
<span>{% trans "Skip" %}</span>
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-step-forward"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="source.isQueue">
|
||||
<hr>
|
||||
<h6 class="title is-6 is-marginless">{% trans "Add sound" %}</h6>
|
||||
<form class="columns" @submit.prevent="source.push($event.target.elements['sound_id'].value)">
|
||||
<div class="column field is-small">
|
||||
<a-autocomplete url="{% url "admin:api:streamer-queue-autocomplete-push" station_pk=station.pk %}?q=${query}"
|
||||
class="is-fullwidth"
|
||||
:model="Sound" field="name" value-field="sound_id" value-attr="id"
|
||||
{# FIXME dirty hack awaiting the vue component #}
|
||||
placeholder="{% trans "Select a sound" %}"></a-autocomplete>
|
||||
<p class="help">
|
||||
{% trans "Add a sound to the queue (queue may start playing)" %}
|
||||
</p>
|
||||
{# TODO: help text about how it works #}
|
||||
</div>
|
||||
<div class="column control is-one-fifth">
|
||||
<button type="submit" class="button is-primary">
|
||||
<span class="icon">
|
||||
<span class="fas fa-plus"></span>
|
||||
</span>
|
||||
<span>{% trans "Add" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="source.queue.length">
|
||||
<h6 class="title is-6 is-marginless">{% trans "Sounds in queue" %}</h6>
|
||||
<table class="table is-fullwidth"><tbody>
|
||||
<tr v-for="[index, request] in source.queue.entries()">
|
||||
<td :class="{'has-text-weight-semibold': index==0 }">
|
||||
<span v-if="index==0" class="far fa-play-circle"></span>
|
||||
<span>[[ request.data.uri ]]</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-two-fifths">
|
||||
<h6 class="subtitle is-6 is-marginless">Metadata</h6>
|
||||
<table class="table has-background-transparent">
|
||||
<tbody>
|
||||
<tr><th class="has-text-right has-text-nowrap">
|
||||
{% trans "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 || "—" ]]
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="source.data.air_time">
|
||||
<th class="has-text-right has-text-nowrap">
|
||||
{% trans "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 has-text-nowrap">
|
||||
{% trans "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 has-text-nowrap">
|
||||
{% trans "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)) || '—' ]]
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></section>
|
||||
|
39
aircox_streamer/templates/aircox_streamer/streamer.html
Normal file
39
aircox_streamer/templates/aircox_streamer/streamer.html
Normal file
@ -0,0 +1,39 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}
|
||||
<script src="{% static "aircox/streamer.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}{{ block.super }}
|
||||
<div id="app" data-api-url="{% url "admin:api:streamer-list" %}">
|
||||
<div class="navbar toolbar">
|
||||
<div class="navbar-start">
|
||||
<span class="navbar-item control">
|
||||
<button class="button">
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-sync"></span>
|
||||
</span>
|
||||
<span>{% trans "Reload" %}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="select navbar-item">
|
||||
<select ref="selectStreamer" @change.native="selectStreamer" class="control"
|
||||
title="{% trans "Select a station" %}"
|
||||
aria-label="{% trans "Select a station" %}">
|
||||
<option v-for="streamer of streamers" :value="streamer.id">[[ streamer.data.name ]]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="streamer">
|
||||
<template v-for="source in sources">
|
||||
{% include "aircox_streamer/source_item.html" %}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
22
aircox_streamer/urls.py
Normal file
22
aircox_streamer/urls.py
Normal file
@ -0,0 +1,22 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from . import viewsets
|
||||
from .views import StreamerAdminView
|
||||
|
||||
|
||||
admin.site.route_view('tools/streamer', StreamerAdminView.as_view(),
|
||||
'tools-streamer', label=_('Streamer Monitor'))
|
||||
|
||||
streamer_prefix = 'streamer/(?P<station_pk>[0-9]+)/'
|
||||
|
||||
|
||||
router = admin.site.router
|
||||
router.register(streamer_prefix + 'playlist', viewsets.PlaylistSourceViewSet,
|
||||
basename='streamer-playlist')
|
||||
router.register(streamer_prefix + 'queue', viewsets.QueueSourceViewSet,
|
||||
basename='streamer-queue')
|
||||
router.register('streamer', viewsets.StreamerViewSet, basename='streamer')
|
||||
|
||||
urls = []
|
||||
|
@ -1,3 +1,11 @@
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from aircox.views.admin import BaseAdminView
|
||||
|
||||
|
||||
class StreamerAdminView(BaseAdminView, TemplateView):
|
||||
template_name = 'aircox_streamer/streamer.html'
|
||||
title = _('Streamer Monitor')
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
@ -1,12 +1,16 @@
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from aircox import controllers
|
||||
from aircox.models import Station
|
||||
from aircox.models import Sound, Station
|
||||
from aircox.serializers import SoundSerializer
|
||||
from . import controllers
|
||||
from .serializers import *
|
||||
|
||||
|
||||
@ -52,16 +56,17 @@ class Streamers:
|
||||
self.date = now + self.timeout
|
||||
|
||||
def get(self, key, default=None):
|
||||
self.fetch()
|
||||
return self.streamers.get(key, default)
|
||||
|
||||
def values(self):
|
||||
self.fetch()
|
||||
return self.streamers.values()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.streamers[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.streamers
|
||||
|
||||
|
||||
streamers = Streamers()
|
||||
|
||||
@ -70,22 +75,25 @@ class BaseControllerAPIView(viewsets.ViewSet):
|
||||
permission_classes = (IsAdminUser,)
|
||||
serializer = None
|
||||
streamer = None
|
||||
object = None
|
||||
|
||||
def get_streamer(self, pk=None):
|
||||
streamer = streamers.get(self.request.pk if pk is None else pk)
|
||||
if not streamer:
|
||||
def get_streamer(self, request, station_pk=None, **kwargs):
|
||||
streamers.fetch()
|
||||
id = int(request.station.pk if station_pk is None else station_pk)
|
||||
if id not in streamers:
|
||||
raise Http404('station not found')
|
||||
return streamer
|
||||
return streamers[id]
|
||||
|
||||
def get_serializer(self, obj, **kwargs):
|
||||
return self.serializer(obj, **kwargs)
|
||||
def get_serializer(self, **kwargs):
|
||||
return self.serializer(self.object, **kwargs)
|
||||
|
||||
def serialize(self, obj, **kwargs):
|
||||
serializer = self.get_serializer(obj, **kwargs)
|
||||
self.object = obj
|
||||
serializer = self.get_serializer(**kwargs)
|
||||
return serializer.data
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.streamer = self.get_streamer(request.station.pk)
|
||||
def dispatch(self, request, *args, station_pk=None, **kwargs):
|
||||
self.streamer = self.get_streamer(request, station_pk, **kwargs)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
@ -97,10 +105,19 @@ class StreamerViewSet(BaseControllerAPIView):
|
||||
serializer = StreamerSerializer
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
return self.serialize(self.streamer)
|
||||
return Response(self.serialize(self.streamer))
|
||||
|
||||
def list(self, request):
|
||||
return self.serialize(streamers.values(), many=True)
|
||||
def list(self, request, pk=None):
|
||||
return Response({
|
||||
'results': self.serialize(streamers.values(), many=True)
|
||||
})
|
||||
|
||||
def dispatch(self, request, *args, pk=None, **kwargs):
|
||||
if pk is not None:
|
||||
kwargs.setdefault('station_pk', pk)
|
||||
self.streamer = self.get_streamer(request, **kwargs)
|
||||
self.object = self.streamer
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SourceViewSet(BaseControllerAPIView):
|
||||
@ -108,38 +125,46 @@ class SourceViewSet(BaseControllerAPIView):
|
||||
model = controllers.Source
|
||||
|
||||
def get_sources(self):
|
||||
return (s for s in self.streamer.souces if isinstance(s, self.model))
|
||||
return (s for s in self.streamer.sources if isinstance(s, self.model))
|
||||
|
||||
def get_source(self, pk):
|
||||
source = next((source for source in self.get_sources()
|
||||
if source.pk == pk), None)
|
||||
if source.id == pk), None)
|
||||
if source is None:
|
||||
raise Http404('source `%s` not found' % pk)
|
||||
return source
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
source = self.get_source(pk)
|
||||
return self.serialize(source)
|
||||
self.object = self.get_source(pk)
|
||||
return Response(self.serialize())
|
||||
|
||||
def list(self, request):
|
||||
return self.serialize(self.get_sources(), many=True)
|
||||
return Response({
|
||||
'results': self.serialize(self.get_sources(), many=True)
|
||||
})
|
||||
|
||||
def _run(self, pk, action):
|
||||
source = self.object = self.get_source(pk)
|
||||
action(source)
|
||||
source.fetch()
|
||||
return Response(self.serialize(source))
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def sync(self, request, pk):
|
||||
self.get_source(pk).sync()
|
||||
return self._run(pk, lambda s: s.sync())
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def skip(self, request, pk):
|
||||
self.get_source(pk).skip()
|
||||
return self._run(pk, lambda s: s.skip())
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def restart(self, request, pk):
|
||||
self.get_source(pk).restart()
|
||||
return self._run(pk, lambda s: s.restart())
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def seek(self, request, pk):
|
||||
count = request.POST['seek']
|
||||
self.get_source(pk).seek(count)
|
||||
return self._run(pk, lambda s: s.seek(count))
|
||||
|
||||
|
||||
class PlaylistSourceViewSet(SourceViewSet):
|
||||
@ -151,8 +176,26 @@ class QueueSourceViewSet(SourceViewSet):
|
||||
serializer = QueueSourceSerializer
|
||||
model = controllers.QueueSource
|
||||
|
||||
def get_sound_queryset(self):
|
||||
return Sound.objects.station(self.request.station).archive()
|
||||
|
||||
@action(detail=False, url_path='autocomplete/push',
|
||||
url_name='autocomplete-push')
|
||||
def autcomplete_push(self, request):
|
||||
query = request.GET.get('q')
|
||||
qs = self.get_sound_queryset().search(query)
|
||||
serializer = SoundSerializer(qs, many=True, context={
|
||||
'request': self.request
|
||||
})
|
||||
return Response({'results': serializer.data})
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def push(self, request, pk):
|
||||
self.get_source(pk).push()
|
||||
if not request.data.get('sound_id'):
|
||||
raise ValidationError('missing "sound_id" POST data')
|
||||
|
||||
sound = get_object_or_404(self.get_sound_queryset(),
|
||||
pk=request.data['sound_id'])
|
||||
return self._run(
|
||||
pk, lambda s: s.push(sound.path) if sound.path else None)
|
||||
|
||||
|
Reference in New Issue
Block a user