actions & action button automatic generation; 'play' & 'listen' button on diffusions work
This commit is contained in:
parent
8ff67fe68a
commit
e971f3f0b5
|
@ -265,7 +265,7 @@ class List(Section):
|
||||||
message_empty = _('nothing')
|
message_empty = _('nothing')
|
||||||
paginate_by = 4
|
paginate_by = 4
|
||||||
|
|
||||||
fields = [ 'date', 'time', 'image', 'title', 'content', 'info' ]
|
fields = [ 'date', 'time', 'image', 'title', 'content', 'info', 'actions' ]
|
||||||
image_size = '64x64'
|
image_size = '64x64'
|
||||||
truncate = 16
|
truncate = 16
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,77 @@
|
||||||
|
/// Actions manager
|
||||||
|
/// This class is used to register actions and to execute them when it is
|
||||||
|
/// triggered by the user.
|
||||||
|
var Actions = {
|
||||||
|
registry: {},
|
||||||
|
|
||||||
|
/// Add an handler for a given action
|
||||||
|
register: function(id, symbol, title, handler) {
|
||||||
|
this.registry[id] = {
|
||||||
|
symbol: symbol,
|
||||||
|
title: title,
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/// Init an existing action HTML element
|
||||||
|
init_action: function(item, action_id, data, url) {
|
||||||
|
var action = this.registry[action_id];
|
||||||
|
if(!action)
|
||||||
|
return;
|
||||||
|
item.title = action.title;
|
||||||
|
item.innerHTML = (action.symbol || '') + '<label>' +
|
||||||
|
action.title + '</label>';
|
||||||
|
item.data = data;
|
||||||
|
item.className = 'action';
|
||||||
|
if(url)
|
||||||
|
item.href = url;
|
||||||
|
item.setAttribute('action', action_id);
|
||||||
|
item.addEventListener('click', Actions.run, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Add an action to the given item
|
||||||
|
add_action: function(item, action_id, data, url) {
|
||||||
|
var actions = item.querySelector('.actions');
|
||||||
|
if(actions && actions.querySelector('[action="' + action_id + '"]'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = document.createElement('a');
|
||||||
|
this.init_action(item, action_id, data, url);
|
||||||
|
actions.appendChild(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Run an action from the given event -- ! this can be undefined
|
||||||
|
run: function(event) {
|
||||||
|
var item = event.target;
|
||||||
|
var action = item.hasAttribute('action') &&
|
||||||
|
item.getAttribute('action');
|
||||||
|
if(!action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
|
action = Actions.registry[action];
|
||||||
|
if(!action)
|
||||||
|
return
|
||||||
|
|
||||||
|
data = item.data || item.dataset;
|
||||||
|
action.handler(data, item);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function(event) {
|
||||||
|
var items = document.querySelectorAll('.action[action]');
|
||||||
|
for(var i = 0; i < items.length; i++) {
|
||||||
|
var item = items[i];
|
||||||
|
var action_id = item.getAttribute('action');
|
||||||
|
var data = item.dataset;
|
||||||
|
Actions.init_action(item, action_id, data);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
|
||||||
/// Small utility used to make XMLHttpRequests, and map results on objects.
|
/// Small utility used to make XMLHttpRequests, and map results on objects.
|
||||||
|
|
|
@ -4,63 +4,72 @@
|
||||||
|
|
||||||
{% with object|downcast as object %}
|
{% with object|downcast as object %}
|
||||||
<li {% if object.css_class %}class="{{ object.css_class }}"{% endif %}
|
<li {% if object.css_class %}class="{{ object.css_class }}"{% endif %}
|
||||||
{% for k, v in object.attrs.items %}
|
{% for k, v in object.attrs.items %}
|
||||||
{{ k }} = "{{ v|addslashes }}"
|
{{ k }} = "{{ v|addslashes }}"
|
||||||
{% endfor %} >
|
{% endfor %} >
|
||||||
{% if object.url %}
|
{% if object.url %}
|
||||||
<a class="url" href="{{ object.url }}">
|
<a class="url" href="{{ object.url }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'image' in list.fields and object.image %}
|
{% if 'image' in list.fields and object.image %}
|
||||||
<img class="image" src="{% thumbnail object.image list.image_size crop %}">
|
<img class="image" src="{% thumbnail object.image list.image_size crop %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
{% if 'title' in list.fields and object.title %}
|
{% if 'title' in list.fields and object.title %}
|
||||||
<h2 class="title">{{ object.title }}</h2>
|
<h2 class="title">{{ object.title }}</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if 'content' in list.fields and object.content %}
|
{% if 'content' in list.fields and object.content %}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% if list.truncate %}
|
{% if list.truncate %}
|
||||||
{{ object.content|striptags|truncatewords:list.truncate }}
|
{{ object.content|striptags|truncatewords:list.truncate }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ object.content|striptags }}
|
{{ object.content|striptags }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
{% if object.date and 'date' in list.fields or 'time' in list.fields %}
|
{% if object.date and 'date' in list.fields or 'time' in list.fields %}
|
||||||
<time datetime="{{ object.date }}">
|
<time datetime="{{ object.date }}">
|
||||||
{% if 'date' in list.fields %}
|
{% if 'date' in list.fields %}
|
||||||
<span class="date">
|
<span class="date">
|
||||||
{{ object.date|date:'D. d F' }}
|
{{ object.date|date:'D. d F' }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'time' in list.fields %}
|
{% if 'time' in list.fields %}
|
||||||
<span class="time">
|
<span class="time">
|
||||||
{{ object.date|date:'H:i' }}
|
{{ object.date|date:'H:i' }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</time>
|
</time>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if object.author and 'author' in list.fields %}
|
{% if object.author and 'author' in list.fields %}
|
||||||
<span class="author">
|
<span class="author">
|
||||||
{{ object.author }}
|
{{ object.author }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if object.info and 'info' in list.fields %}
|
{% if object.info and 'info' in list.fields %}
|
||||||
<span class="info">
|
<span class="info">
|
||||||
{{ object.info }}
|
{{ object.info }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if object.url %}
|
{% if object.actions and 'actions' in list.fields %}
|
||||||
</a>
|
<div class="actions">
|
||||||
{% endif %}
|
{% for action_id,action_data in object.actions.items %}
|
||||||
|
<a class="action" action="{{action_id}}"
|
||||||
|
{% for k,v in action_data.items %}data-{{ k }}="{{ v }}"{% endfor %}></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if object.url %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,6 @@ class PostListView(BaseView, ListView):
|
||||||
self.list = sections.List(
|
self.list = sections.List(
|
||||||
truncate = 32,
|
truncate = 32,
|
||||||
paginate_by = 0,
|
paginate_by = 0,
|
||||||
fields = ['date', 'time', 'image', 'title', 'content'],
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.list = self.list(paginate_by = 0)
|
self.list = self.list(paginate_by = 0)
|
||||||
|
|
|
@ -64,7 +64,8 @@ class Monitor:
|
||||||
playlist = diff.playlist
|
playlist = diff.playlist
|
||||||
if played_sounds:
|
if played_sounds:
|
||||||
diff.played = [ sound.related_object.path
|
diff.played = [ sound.related_object.path
|
||||||
for sound in sound_logs[0:len(playlist)] ]
|
for sound in sound_logs[0:len(playlist)]
|
||||||
|
if sound.type = program.Logs.Type.switch ]
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -111,6 +112,13 @@ class Monitor:
|
||||||
# playlist update
|
# playlist update
|
||||||
if dealer.playlist != playlist:
|
if dealer.playlist != playlist:
|
||||||
dealer.playlist = playlist
|
dealer.playlist = playlist
|
||||||
|
if next_diff:
|
||||||
|
cl.log(
|
||||||
|
type = programs.Log.Type.load,
|
||||||
|
source = dealer.id,
|
||||||
|
date = now,
|
||||||
|
related_object = next_diff
|
||||||
|
)
|
||||||
|
|
||||||
# dealer.on when next_diff.start <= now
|
# dealer.on when next_diff.start <= now
|
||||||
if next_diff and not dealer.on and next_diff.start <= now:
|
if next_diff and not dealer.on and next_diff.start <= now:
|
||||||
|
@ -118,18 +126,16 @@ class Monitor:
|
||||||
for source in controller.streams.values():
|
for source in controller.streams.values():
|
||||||
source.skip()
|
source.skip()
|
||||||
cl.log(
|
cl.log(
|
||||||
|
type = programs.Log.Type.play,
|
||||||
source = dealer.id,
|
source = dealer.id,
|
||||||
date = now,
|
date = now,
|
||||||
comment = 'trigger diffusion to liquidsoap; '
|
|
||||||
'skip other streams',
|
|
||||||
related_object = next_diff,
|
related_object = next_diff,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_source (cl, source):
|
def run_source (cl, source):
|
||||||
"""
|
"""
|
||||||
Keep trace of played sounds on the given source. For the moment we only
|
Keep trace of played sounds on the given source.
|
||||||
keep track of known sounds.
|
|
||||||
"""
|
"""
|
||||||
# TODO: repetition of the same sound out of an interval of time
|
# TODO: repetition of the same sound out of an interval of time
|
||||||
last_log = programs.Log.objects.filter(
|
last_log = programs.Log.objects.filter(
|
||||||
|
@ -150,16 +156,16 @@ class Monitor:
|
||||||
return
|
return
|
||||||
|
|
||||||
sound = programs.Sound.objects.filter(path = on_air)
|
sound = programs.Sound.objects.filter(path = on_air)
|
||||||
if not sound:
|
kwargs = {
|
||||||
return
|
'type': programs.Log.Type.play,
|
||||||
|
'source': source.id,
|
||||||
sound = sound[0]
|
'date': tz.make_aware(tz.datetime.now()),
|
||||||
cl.log(
|
}
|
||||||
source = source.id,
|
if sound:
|
||||||
date = tz.make_aware(tz.datetime.now()),
|
kwargs['related_object'] = sound[0]
|
||||||
comment = 'sound changed',
|
else:
|
||||||
related_object = sound or None,
|
kwargs['comment'] = on_air
|
||||||
)
|
cl.log(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Command (BaseCommand):
|
class Command (BaseCommand):
|
||||||
|
|
|
@ -2,10 +2,10 @@ import os
|
||||||
import socket
|
import socket
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
from django.utils.text import slugify
|
||||||
from django.conf import settings as main_settings
|
from django.conf import settings as main_settings
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
@ -130,7 +130,6 @@ class BaseSource:
|
||||||
return self.update(metadata = r or {})
|
return self.update(metadata = r or {})
|
||||||
|
|
||||||
source = metadata.get('source') or ''
|
source = metadata.get('source') or ''
|
||||||
# FIXME: self.program
|
|
||||||
if hasattr(self, 'program') and self.program \
|
if hasattr(self, 'program') and self.program \
|
||||||
and not source.startswith(self.id):
|
and not source.startswith(self.id):
|
||||||
return -1
|
return -1
|
||||||
|
@ -145,20 +144,19 @@ class Source(BaseSource):
|
||||||
metadata = None
|
metadata = None
|
||||||
|
|
||||||
def __init__(self, controller, program = None, is_dealer = None):
|
def __init__(self, controller, program = None, is_dealer = None):
|
||||||
station = controller.station
|
|
||||||
if is_dealer:
|
if is_dealer:
|
||||||
id, name = '{}_dealer'.format(station.slug), \
|
id, name = '{}_dealer'.format(controller.id), \
|
||||||
'Dealer'
|
'Dealer'
|
||||||
self.is_dealer = True
|
self.is_dealer = True
|
||||||
else:
|
else:
|
||||||
id, name = '{}_stream_{}'.format(station.slug, program.id), \
|
id, name = '{}_stream_{}'.format(controller.id, program.id), \
|
||||||
program.name
|
program.name
|
||||||
|
|
||||||
super().__init__(controller, id, name)
|
super().__init__(controller, id, name)
|
||||||
|
|
||||||
self.program = program
|
self.program = program
|
||||||
self.path = os.path.join(settings.AIRCOX_LIQUIDSOAP_MEDIA,
|
self.path = os.path.join(settings.AIRCOX_LIQUIDSOAP_MEDIA,
|
||||||
station.slug,
|
controller.id,
|
||||||
self.id + '.m3u')
|
self.id + '.m3u')
|
||||||
if program:
|
if program:
|
||||||
self.playlist_from_db()
|
self.playlist_from_db()
|
||||||
|
@ -237,10 +235,6 @@ class Master (BaseSource):
|
||||||
"""
|
"""
|
||||||
A master Source based on a given station
|
A master Source based on a given station
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller):
|
|
||||||
station = controller.station
|
|
||||||
super().__init__(controller, station.slug, station.name)
|
|
||||||
|
|
||||||
def update(self, metadata = None):
|
def update(self, metadata = None):
|
||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
return super().update(metadata)
|
return super().update(metadata)
|
||||||
|
@ -259,13 +253,12 @@ class Controller:
|
||||||
path = None
|
path = None
|
||||||
|
|
||||||
connector = None
|
connector = None
|
||||||
station = None # the related station
|
master = None # master source
|
||||||
master = None # master source (station's source)
|
|
||||||
dealer = None # dealer source
|
dealer = None # dealer source
|
||||||
streams = None # streams streams
|
streams = None # streams streams
|
||||||
|
|
||||||
# FIXME: used nowhere except in liquidsoap cli to get on air item but is not
|
# FIXME: used nowhere except in liquidsoap cli to get on air item but is not
|
||||||
# correctly
|
# correct
|
||||||
@property
|
@property
|
||||||
def on_air(self):
|
def on_air(self):
|
||||||
return self.master
|
return self.master
|
||||||
|
@ -294,10 +287,9 @@ class Controller:
|
||||||
to the given station; We ensure the existence of the controller's
|
to the given station; We ensure the existence of the controller's
|
||||||
files dir.
|
files dir.
|
||||||
"""
|
"""
|
||||||
self.id = station.slug
|
self.id = slugify(station)
|
||||||
self.name = station
|
self.name = station
|
||||||
self.path = os.path.join(settings.AIRCOX_LIQUIDSOAP_MEDIA,
|
self.path = os.path.join(settings.AIRCOX_LIQUIDSOAP_MEDIA, self.id)
|
||||||
slugify(station))
|
|
||||||
|
|
||||||
self.outputs = models.Output.objects.all()
|
self.outputs = models.Output.objects.all()
|
||||||
|
|
||||||
|
@ -360,6 +352,7 @@ class Controller:
|
||||||
'log_script': log_script,
|
'log_script': log_script,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# FIXME: remove this crappy thing
|
||||||
data = render_to_string('aircox/liquidsoap/station.liq', context)
|
data = render_to_string('aircox/liquidsoap/station.liq', context)
|
||||||
data = re.sub(r'\s*\\\n', r'#\\n#', data)
|
data = re.sub(r'\s*\\\n', r'#\\n#', data)
|
||||||
data = data.replace('\n', '')
|
data = data.replace('\n', '')
|
||||||
|
|
23
notes.md
23
notes.md
|
@ -1,11 +1,3 @@
|
||||||
- sounds monitor: max_size of path, take in account
|
|
||||||
- logs: archive functionnality + track stats for diffusions
|
|
||||||
- debug/prod configuration
|
|
||||||
|
|
||||||
# TODO ajd
|
|
||||||
- website/sections Diffusions/prepare\_object\_list -> sounds
|
|
||||||
- players' buttons
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
- general:
|
- general:
|
||||||
|
@ -14,6 +6,7 @@
|
||||||
|
|
||||||
- programs:
|
- programs:
|
||||||
- schedule changes -> update later diffusions according to the new schedule
|
- schedule changes -> update later diffusions according to the new schedule
|
||||||
|
- stream disable -> remote control on liquidsoap
|
||||||
- tests:
|
- tests:
|
||||||
- sound_monitor
|
- sound_monitor
|
||||||
|
|
||||||
|
@ -25,26 +18,34 @@
|
||||||
- config generation and sound diffusion
|
- config generation and sound diffusion
|
||||||
|
|
||||||
- cms:
|
- cms:
|
||||||
- switch to abstract class and remove qcombine (or keep it smw else)?
|
|
||||||
- empty content -> empty string
|
- empty content -> empty string
|
||||||
- update documentation:
|
- update documentation:
|
||||||
- cms.script
|
- cms.script
|
||||||
- cms.exposure; make it right, see nomenclature, + docstring
|
- cms.exposure; make it right, see nomenclature, + docstring
|
||||||
- admin cms
|
- admin cms
|
||||||
- sections:
|
- sections:
|
||||||
- calendar title update
|
|
||||||
- article list with the focus
|
- article list with the focus
|
||||||
-> set html attribute based on values that are public
|
-> set html attribute based on values that are public
|
||||||
|
|
||||||
- website:
|
- website:
|
||||||
- render schedule does not get the correct list
|
- render schedule does not get the correct list
|
||||||
|
-> postlistview has not the same queryset as website/sections/schedule
|
||||||
- diffusions:
|
- diffusions:
|
||||||
- filter sounds for undiffused diffusions
|
- filter sounds for undiffused diffusions
|
||||||
- print sounds of diffusions
|
- print sounds of diffusions
|
||||||
- print program's name in lists
|
- print program's name in lists
|
||||||
- player:
|
- player:
|
||||||
- "listen" + "favorite" buttons made easy + automated
|
|
||||||
- mixcloud
|
- mixcloud
|
||||||
- seek bar
|
- seek bar
|
||||||
- list of played diffusions and tracks when non-stop;
|
- list of played diffusions and tracks when non-stop;
|
||||||
|
|
||||||
|
# Later todo
|
||||||
|
- sounds monitor: max_size of path, take in account
|
||||||
|
- logs: archive functionnality
|
||||||
|
- track stats for diffusions
|
||||||
|
- debug/prod configuration
|
||||||
|
- player support diffusions with multiple archive files
|
||||||
|
- view as grid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ class SoundAdmin(NameableAdmin):
|
||||||
fields = None
|
fields = None
|
||||||
list_display = ['id', 'name', 'duration', 'type', 'mtime', 'good_quality', 'removed']
|
list_display = ['id', 'name', 'duration', 'type', 'mtime', 'good_quality', 'removed']
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, { 'fields': NameableAdmin.fields + ['path', 'type'] } ),
|
(None, { 'fields': NameableAdmin.fields + ['path', 'type', 'diffusion'] } ),
|
||||||
(None, { 'fields': ['embed', 'duration', 'mtime'] }),
|
(None, { 'fields': ['embed', 'duration', 'mtime'] }),
|
||||||
(None, { 'fields': ['removed', 'good_quality' ] } )
|
(None, { 'fields': ['removed', 'good_quality' ] } )
|
||||||
]
|
]
|
||||||
|
|
|
@ -81,8 +81,8 @@ class Track(Nameable):
|
||||||
_('artist'),
|
_('artist'),
|
||||||
max_length = 128,
|
max_length = 128,
|
||||||
)
|
)
|
||||||
# position can be used to specify a position in seconds for non-
|
# position can be used to specify a position in seconds for stream
|
||||||
# stop programs or a position in the playlist
|
# programs or a position in the playlist
|
||||||
position = models.SmallIntegerField(
|
position = models.SmallIntegerField(
|
||||||
default = 0,
|
default = 0,
|
||||||
help_text=_('position in the playlist'),
|
help_text=_('position in the playlist'),
|
||||||
|
@ -172,7 +172,7 @@ class Sound(Nameable):
|
||||||
# path = self._meta.get_field('path').path
|
# path = self._meta.get_field('path').path
|
||||||
path = self.path.replace(main_settings.MEDIA_ROOT, '', 1)
|
path = self.path.replace(main_settings.MEDIA_ROOT, '', 1)
|
||||||
#path = self.path.replace(path, '', 1)
|
#path = self.path.replace(path, '', 1)
|
||||||
return path
|
return main_settings.MEDIA_URL + '/' + path
|
||||||
|
|
||||||
def file_exists(self):
|
def file_exists(self):
|
||||||
"""
|
"""
|
||||||
|
@ -684,8 +684,30 @@ class Diffusion(models.Model):
|
||||||
|
|
||||||
class Log(models.Model):
|
class Log(models.Model):
|
||||||
"""
|
"""
|
||||||
Log a played sound start and stop, or a single message
|
Log sounds and diffusions that are played in the streamer. It
|
||||||
|
can also be used for other purposes.
|
||||||
"""
|
"""
|
||||||
|
class Type(IntEnum):
|
||||||
|
stop = 0x00
|
||||||
|
"""
|
||||||
|
Source has been stopped (only when there is no more sound)
|
||||||
|
"""
|
||||||
|
play = 0x01
|
||||||
|
"""
|
||||||
|
Source has been started/changed and is running related_object
|
||||||
|
If no related_object is available, comment is used to designate
|
||||||
|
the sound.
|
||||||
|
"""
|
||||||
|
load = 0x02
|
||||||
|
"""
|
||||||
|
Source starts to be preload related_object
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = models.SmallIntegerField(
|
||||||
|
verbose_name = _('type'),
|
||||||
|
choices = [ (int(y), _(x)) for x,y in Type.__members__.items() ],
|
||||||
|
blank = True, null = True,
|
||||||
|
)
|
||||||
source = models.CharField(
|
source = models.CharField(
|
||||||
_('source'),
|
_('source'),
|
||||||
max_length = 64,
|
max_length = 64,
|
||||||
|
@ -693,10 +715,11 @@ class Log(models.Model):
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
'date',
|
_('date'),
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
)
|
)
|
||||||
comment = models.CharField(
|
comment = models.CharField(
|
||||||
|
_('comment'),
|
||||||
max_length = 512,
|
max_length = 512,
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -115,7 +115,17 @@ class Diffusions(sections.List):
|
||||||
post.title = ': ' + post.title if post.title else \
|
post.title = ': ' + post.title if post.title else \
|
||||||
' // ' + post.related.start.strftime('%A %d %B')
|
' // ' + post.related.start.strftime('%A %d %B')
|
||||||
post.title = name + post.title
|
post.title = name + post.title
|
||||||
|
|
||||||
# sounds
|
# sounds
|
||||||
|
pl = post.related.get_archives()
|
||||||
|
if pl:
|
||||||
|
item = { 'title': post.title, 'stream': pl[0].url,
|
||||||
|
'url': post.url() }
|
||||||
|
post.actions = {
|
||||||
|
'sound.play': item,
|
||||||
|
'sound.mark': item,
|
||||||
|
}
|
||||||
|
|
||||||
return object_list
|
return object_list
|
||||||
|
|
||||||
def get_object_list(self):
|
def get_object_list(self):
|
||||||
|
|
|
@ -99,10 +99,11 @@
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlist .actions label,
|
||||||
#playlist-live .actions,
|
#playlist-live .actions,
|
||||||
#playlist-recents .actions a.action[action="remove"],
|
#playlist-recents .actions a.action[action="remove"],
|
||||||
#playlist-marked .actions a.action[action="mark"],
|
#playlist-marked .actions a.action[action="sound.mark"],
|
||||||
.playlist .actions a.action[action="play"],
|
.playlist .actions a.action[action="sound.play"],
|
||||||
.playlist .actions a.url:not([href]),
|
.playlist .actions a.url:not([href]),
|
||||||
.playlist .actions a.url[href=""] {
|
.playlist .actions a.url[href=""] {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -113,10 +114,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div id="player">
|
<div id="player">
|
||||||
<div class="actions sounds" style="display: none;">
|
|
||||||
<a class="action" action="mark" title="{% trans "mark this sound" %}">★</a>
|
|
||||||
<a class="action" action="play" title="{% trans "play this sound" %}">▶</a>
|
|
||||||
</div>
|
|
||||||
<li class='item' style="display: none;">
|
<li class='item' style="display: none;">
|
||||||
<h2 class="title"></h2>
|
<h2 class="title"></h2>
|
||||||
<div class="info"></div>
|
<div class="info"></div>
|
||||||
|
@ -455,7 +452,7 @@ player = {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(data.playlist)
|
if(data.playlist)
|
||||||
this.select_playlist(this[data.playlist]);
|
this.select_playlist(this[data.selected_playlist]);
|
||||||
if(data.stream) {
|
if(data.stream) {
|
||||||
item = this.playlist.find(data.stream, true);
|
item = this.playlist.find(data.stream, true);
|
||||||
item && this.select(item, false);
|
item && this.select(item, false);
|
||||||
|
@ -465,7 +462,7 @@ player = {
|
||||||
|
|
||||||
save: function() {
|
save: function() {
|
||||||
playerStore.set('player', {
|
playerStore.set('player', {
|
||||||
'playlist': this.playlist.name,
|
'selected_playlist': this.__playlist && this.__playlist.name,
|
||||||
'stream': this.item && this.item.stream,
|
'stream': this.item && this.item.stream,
|
||||||
'single': this.controls.single.checked,
|
'single': this.controls.single.checked,
|
||||||
});
|
});
|
||||||
|
@ -477,12 +474,10 @@ player = {
|
||||||
var player = this.player;
|
var player = this.player;
|
||||||
var audio = this.audio;
|
var audio = this.audio;
|
||||||
|
|
||||||
if(audio.paused) {
|
if(audio.paused)
|
||||||
audio.play();
|
audio.play();
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
audio.pause();
|
audio.pause();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
__ask_to_seek(item) {
|
__ask_to_seek(item) {
|
||||||
|
@ -523,17 +518,17 @@ player = {
|
||||||
|
|
||||||
/// Select the next track in the current playlist, eventually play it
|
/// Select the next track in the current playlist, eventually play it
|
||||||
next: function(play = true) {
|
next: function(play = true) {
|
||||||
var playlist = this.playlist;
|
var playlist = this.__playlist;
|
||||||
if(playlist == this.live)
|
if(playlist == this.live)
|
||||||
return
|
return
|
||||||
|
|
||||||
var index = this.playlist.items.indexOf(this.item);
|
var index = this.__playlist.items.indexOf(this.item);
|
||||||
if(index == -1)
|
if(index == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
index--;
|
index--;
|
||||||
if(index >= 0)
|
if(index >= 0)
|
||||||
this.select(this.playlist.items[index], play);
|
this.select(this.__playlist.items[index], play);
|
||||||
},
|
},
|
||||||
|
|
||||||
/// remove selection using the given selector.
|
/// remove selection using the given selector.
|
||||||
|
@ -549,7 +544,7 @@ player = {
|
||||||
this.__unselect('.playlists nav .tab[selected]');
|
this.__unselect('.playlists nav .tab[selected]');
|
||||||
this.__unselect('.playlists .playlist[selected]');
|
this.__unselect('.playlists .playlist[selected]');
|
||||||
|
|
||||||
this.playlist = playlist;
|
this.__playlist = playlist;
|
||||||
if(playlist) {
|
if(playlist) {
|
||||||
playlist.playlist.setAttribute('selected', 'true');
|
playlist.playlist.setAttribute('selected', 'true');
|
||||||
playlist.tab.setAttribute('selected', 'true');
|
playlist.tab.setAttribute('selected', 'true');
|
||||||
|
@ -574,51 +569,28 @@ player = {
|
||||||
|
|
||||||
/// add sound actions to a given element
|
/// add sound actions to a given element
|
||||||
add_actions: function(item, container) {
|
add_actions: function(item, container) {
|
||||||
var player = this;
|
Actions.add_action(container, 'sound.mark', item);
|
||||||
|
Actions.add_action(container, 'sound.play', item, item.stream);
|
||||||
var actions = player.player.querySelector('.actions')
|
// TODO: remove from playlist
|
||||||
var elm = container.querySelector('.actions');
|
|
||||||
if(elm) {
|
|
||||||
var actions = actions.childNodes;
|
|
||||||
for(var i = 0; i < actions.length; i++)
|
|
||||||
elm.appendChild(actions[i].cloneNode(true));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
elm = elm.cloneNode(true);
|
|
||||||
elm.removeAttribute('style');
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.addEventListener('click', function(event) {
|
|
||||||
var action = event.target.getAttribute('action');
|
|
||||||
if(!action)
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
|
|
||||||
switch(action) {
|
|
||||||
case 'mark':
|
|
||||||
player.marked.add(item);
|
|
||||||
break;
|
|
||||||
case 'play':
|
|
||||||
item = player.playlist.add(item);
|
|
||||||
player.select_playlist(player.playlist);
|
|
||||||
player.select(item, true);
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
item.playlist.remove(item);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
if(!elm.parentNode)
|
|
||||||
container.appendChild(elm);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
player.init('player')
|
Actions.register('sound.mark', '★', '{% trans "add to my playlist" %}',
|
||||||
|
function(item) {
|
||||||
|
player.marked.add(item);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Actions.register('sound.play', '▶', '{% trans "listen" %}',
|
||||||
|
function(item) {
|
||||||
|
item = player.playlist.add(item);
|
||||||
|
player.select_playlist(player.playlist);
|
||||||
|
player.select(item, true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
player.init('player');
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user