forked from rc/aircox
		
	actions & action button automatic generation; 'play' & 'listen' button on diffusions work
This commit is contained in:
		@ -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.
 | 
				
			||||||
 | 
				
			|||||||
@ -58,6 +58,15 @@
 | 
				
			|||||||
        {% endif %}
 | 
					        {% endif %}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% if object.actions and 'actions' in list.fields %}
 | 
				
			||||||
 | 
					        <div class="actions">
 | 
				
			||||||
 | 
					        {% 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 %}
 | 
					    {% if object.url %}
 | 
				
			||||||
    </a>
 | 
					    </a>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
				
			|||||||
@ -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 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user