work on admin interface, player, list of sounds
This commit is contained in:
parent
f5dbc93f7f
commit
021b2a116a
|
@ -62,7 +62,7 @@ class Command (BaseCommand):
|
||||||
start__gt = tz.now().date() - tz.timedelta(days = 20),
|
start__gt = tz.now().date() - tz.timedelta(days = 20),
|
||||||
page__isnull = True,
|
page__isnull = True,
|
||||||
initial__isnull = True
|
initial__isnull = True
|
||||||
)
|
).exclude(type = Diffusion.Type.unconfirmed)
|
||||||
for diffusion in qs:
|
for diffusion in qs:
|
||||||
if not diffusion.program.page.count():
|
if not diffusion.program.page.count():
|
||||||
if not hasattr(diffusion.program, '__logged_diff_error'):
|
if not hasattr(diffusion.program, '__logged_diff_error'):
|
||||||
|
|
|
@ -31,6 +31,7 @@ import aircox.programs.models as programs
|
||||||
import aircox.controllers.models as controllers
|
import aircox.controllers.models as controllers
|
||||||
import aircox.cms.settings as settings
|
import aircox.cms.settings as settings
|
||||||
|
|
||||||
|
from aircox.cms.utils import image_url
|
||||||
from aircox.cms.sections import *
|
from aircox.cms.sections import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,6 +164,15 @@ class Comment(models.Model):
|
||||||
_('comment'),
|
_('comment'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# Translators: text shown in the comments list (in admin)
|
||||||
|
return _('{date}, {author}: {content}...').format(
|
||||||
|
author = self.author,
|
||||||
|
date = self.date.strftime('%d %A %Y, %H:%M'),
|
||||||
|
content = self.content[:128]
|
||||||
|
)
|
||||||
|
|
||||||
def make_safe(self):
|
def make_safe(self):
|
||||||
self.author = bleach.clean(self.author, tags=[])
|
self.author = bleach.clean(self.author, tags=[])
|
||||||
if self.email:
|
if self.email:
|
||||||
|
@ -263,6 +273,15 @@ class Publication(Page):
|
||||||
index.FilterField('show_in_menus'),
|
index.FilterField('show_in_menus'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
return image_url(self.cover, 'fill-64x64')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def small_icon(self):
|
||||||
|
return image_url(self.cover, 'fill-32x32')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def recents(self):
|
def recents(self):
|
||||||
return self.get_children().type(Publication).not_in_menu().live() \
|
return self.get_children().type(Publication).not_in_menu().live() \
|
||||||
|
@ -390,10 +409,8 @@ class Track(programs.Track,Orderable):
|
||||||
diffusion = ParentalKey('DiffusionPage',
|
diffusion = ParentalKey('DiffusionPage',
|
||||||
related_name='tracks')
|
related_name='tracks')
|
||||||
panels = [
|
panels = [
|
||||||
FieldRowPanel([
|
FieldPanel('artist'),
|
||||||
FieldPanel('artist'),
|
FieldPanel('title'),
|
||||||
FieldPanel('title'),
|
|
||||||
]),
|
|
||||||
FieldPanel('tags'),
|
FieldPanel('tags'),
|
||||||
FieldPanel('info'),
|
FieldPanel('info'),
|
||||||
]
|
]
|
||||||
|
@ -428,13 +445,15 @@ class DiffusionPage(Publication):
|
||||||
verbose_name = _('Diffusion')
|
verbose_name = _('Diffusion')
|
||||||
verbose_name_plural = _('Diffusions')
|
verbose_name_plural = _('Diffusions')
|
||||||
|
|
||||||
content_panels = [
|
content_panels = Publication.content_panels + [
|
||||||
FieldPanel('diffusion'),
|
|
||||||
FieldPanel('publish_archive'),
|
|
||||||
] + Publication.content_panels + [
|
|
||||||
InlinePanel('tracks', label=_('Tracks')),
|
InlinePanel('tracks', label=_('Tracks')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
promote_panels = [
|
||||||
|
# FieldPanel('diffusion'),
|
||||||
|
FieldPanel('publish_archive'),
|
||||||
|
] + Publication.promote_panels
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_diffusion(cl, diff, model = None, **kwargs):
|
def from_diffusion(cl, diff, model = None, **kwargs):
|
||||||
model = model or cl
|
model = model or cl
|
||||||
|
@ -519,8 +538,6 @@ class DiffusionPage(Publication):
|
||||||
podcast.public = publish
|
podcast.public = publish
|
||||||
podcast.save()
|
podcast.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -221,19 +221,18 @@ class ListBase(models.Model):
|
||||||
else:
|
else:
|
||||||
qs = qs.descendant_of(related)
|
qs = qs.descendant_of(related)
|
||||||
|
|
||||||
date = self.related.date if hasattr('date', related) else \
|
date = self.related.date if hasattr(related, 'date') else \
|
||||||
self.related.first_published_at
|
self.related.first_published_at
|
||||||
if self.date_filter == self.DateFilter.before_related:
|
if self.date_filter == self.DateFilter.before_related:
|
||||||
qs = qs.filter(date__lt = date)
|
qs = qs.filter(date__lt = date)
|
||||||
elif self.date_filter == self.DateFilter.after_related:
|
elif self.date_filter == self.DateFilter.after_related:
|
||||||
qs = qs.filter(date__gte = date)
|
qs = qs.filter(date__gte = date)
|
||||||
# date
|
# date
|
||||||
else:
|
date = tz.now()
|
||||||
date = tz.now()
|
if self.date_filter == self.DateFilter.previous:
|
||||||
if self.date_filter == self.DateFilter.previous:
|
qs = qs.filter(date__lt = date)
|
||||||
qs = qs.filter(date__lt = date)
|
elif self.date_filter == self.DateFilter.next:
|
||||||
elif self.date_filter == self.DateFilter.next:
|
qs = qs.filter(date__gte = date)
|
||||||
qs = qs.filter(date__gte = date)
|
|
||||||
|
|
||||||
# sort
|
# sort
|
||||||
if self.asc:
|
if self.asc:
|
||||||
|
@ -332,9 +331,7 @@ class ListBase(models.Model):
|
||||||
search = request.GET.get('search')
|
search = request.GET.get('search')
|
||||||
if search:
|
if search:
|
||||||
kwargs['terms'] = search
|
kwargs['terms'] = search
|
||||||
print(search, qs)
|
|
||||||
qs = qs.search(search)
|
qs = qs.search(search)
|
||||||
print(qs.count())
|
|
||||||
|
|
||||||
set('list_selector', kwargs)
|
set('list_selector', kwargs)
|
||||||
|
|
||||||
|
@ -342,7 +339,7 @@ class ListBase(models.Model):
|
||||||
if qs:
|
if qs:
|
||||||
paginator = Paginator(qs, 30)
|
paginator = Paginator(qs, 30)
|
||||||
try:
|
try:
|
||||||
qs = paginator.page('page')
|
qs = paginator.page(request.GET.get('page') or 1)
|
||||||
except PageNotAnInteger:
|
except PageNotAnInteger:
|
||||||
qs = paginator.page(1)
|
qs = paginator.page(1)
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
|
@ -803,7 +800,7 @@ class SectionList(ListBase, SectionRelativeItem):
|
||||||
'list. If empty, does not print an address'),
|
'list. If empty, does not print an address'),
|
||||||
)
|
)
|
||||||
|
|
||||||
panels = SectionItem.panels + [
|
panels = SectionRelativeItem.panels + [
|
||||||
MultiFieldPanel([
|
MultiFieldPanel([
|
||||||
FieldPanel('focus_available'),
|
FieldPanel('focus_available'),
|
||||||
FieldPanel('count'),
|
FieldPanel('count'),
|
||||||
|
@ -815,6 +812,9 @@ class SectionList(ListBase, SectionRelativeItem):
|
||||||
from aircox.cms.models import Publication
|
from aircox.cms.models import Publication
|
||||||
context = super().get_context(request, page)
|
context = super().get_context(request, page)
|
||||||
|
|
||||||
|
if self.is_related:
|
||||||
|
self.related = page
|
||||||
|
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
qs = qs.live()
|
qs = qs.live()
|
||||||
if self.focus_available:
|
if self.focus_available:
|
||||||
|
|
|
@ -24,6 +24,9 @@ ul {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
max-width: 2em;
|
max-width: 2em;
|
||||||
|
@ -67,9 +70,11 @@ nav.menu {
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0em 0em 0.2em black;
|
||||||
}
|
}
|
||||||
.menu.top * {
|
.menu.top * {
|
||||||
vertical-align: middle;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu.top > section {
|
.menu.top > section {
|
||||||
|
@ -81,6 +86,10 @@ nav.menu {
|
||||||
margin: 0.2em 1em;
|
margin: 0.2em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page_left, .page_right {
|
||||||
|
max-width: 16em;
|
||||||
|
}
|
||||||
|
|
||||||
.page_left > section,
|
.page_left > section,
|
||||||
.page_right > section {
|
.page_right > section {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
@ -121,6 +130,12 @@ ul.list {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.list nav {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** content: date list **/
|
/** content: date list **/
|
||||||
.date_list nav {
|
.date_list nav {
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
@ -188,8 +203,6 @@ ul.list {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.comments ul {
|
.comments ul {
|
||||||
margin-top: 2.5em;
|
margin-top: 2.5em;
|
||||||
}
|
}
|
||||||
|
@ -208,19 +221,23 @@ ul.list {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** content: player **/
|
/** content: player **/
|
||||||
.player {
|
.player {
|
||||||
width: 20em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.player:not([seekable]) > .controls {
|
.player:not([seekable]) > .controls > .progress {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.player .controls > * {
|
.player .controls {
|
||||||
margin: 0em 0.2em;
|
margin-top: 1em;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.player .controls > * {
|
||||||
|
margin: 0em 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
.player .controls .single {
|
.player .controls .single {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -245,14 +262,9 @@ ul.list {
|
||||||
border-right: 2px #818181 solid;
|
border-right: 2px #818181 solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .on_air a:not([href]), .on_air a[href=""] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player .playlist .item {
|
.player .playlist .item {
|
||||||
margin: 0em;
|
margin: 0em;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
height: 1em;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,25 +278,20 @@ ul.list {
|
||||||
|
|
||||||
.player .playlist .item .actions {
|
.player .playlist .item .actions {
|
||||||
display: none;
|
display: none;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .playlist .item:hover .actions {
|
.player .playlist .item:hover .actions {
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .playlist .item .info {
|
|
||||||
float: right;
|
|
||||||
width: 2em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player .item:not([selected]) .button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player .item[selected] {
|
.player .item[selected] {
|
||||||
height: auto;
|
border-left: 1px #007EDF solid;
|
||||||
font-size: 1.1em;
|
font-size: 1.0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .item:not([selected]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .button {
|
.player .button {
|
||||||
|
@ -300,36 +307,33 @@ ul.list {
|
||||||
max-height: 2.0em;
|
max-height: 2.0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player:not([state]) .button > img:not(.play),
|
|
||||||
.player[state="paused"] .button > img:not(.play),
|
.player:not([state]) .item[selected] .button > img:not(.play),
|
||||||
.player[state="playing"] .button > img:not(.pause),
|
.player[state="paused"] .item[selected] .button > img:not(.play),
|
||||||
.player[state="loading"] .button > img:not(.loading)
|
.player[state="playing"] .item[selected] .button > img:not(.pause),
|
||||||
|
.player[state="loading"] .item[selected] .button > img:not(.loading)
|
||||||
{
|
{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player[state="loading"] .box .button > img.loading {
|
.player .item:not([selected]) .button > img.play {
|
||||||
animation-duration: 2s;
|
display: block;
|
||||||
animation-iteration-count: infinite;
|
}
|
||||||
animation-name: rotate;
|
.player .item:not([selected]) .button > img:not(.play) {
|
||||||
animation-timing-function: linear;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
.player .list_item.live:hover .actions {
|
||||||
transform: rotate(360deg);
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** content: page **/
|
/** content: page **/
|
||||||
main .body ~ section:not(.comments) {
|
main .body ~ section:not(.comments) {
|
||||||
width: calc(50% - 1em);
|
width: calc(50% - 1em);
|
||||||
float: left;
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ time {
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
padding: 0.1em;
|
||||||
color: #007EDF;
|
color: #007EDF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,14 +82,69 @@ main {
|
||||||
box-shadow: 0em 0em 0.2em black;
|
box-shadow: 0em 0em 0.2em black;
|
||||||
}
|
}
|
||||||
|
|
||||||
main h1 {
|
main h1:not(.detail_title) {
|
||||||
margin: 0em;
|
|
||||||
margin: 0em 0em 0.4em 0em;
|
margin: 0em 0em 0.4em 0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
main .content img.cover {
|
|
||||||
|
main h1.detail_title {
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0.2em;
|
||||||
|
position: relative;
|
||||||
|
left: -0.7em;
|
||||||
|
width: 80%;
|
||||||
|
background-color: rgba(255,255,255,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
main img.detail_cover {
|
||||||
width: calc(100% + 2em);
|
width: calc(100% + 2em);
|
||||||
|
margin-top: -3.3em;
|
||||||
margin-left: -1em;
|
margin-left: -1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** player **/
|
||||||
|
.player[state='playing'] .item[selected] .button > img {
|
||||||
|
animation-duration: 4s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: blink;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
from {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.player[state="loading"] .item[selected] .button > img.loading {
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: rotate;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,13 @@ function duration_str(seconds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Sound(title, detail, duration, streams) {
|
function Sound(title, detail, duration, streams, cover, on_air) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.detail = detail;
|
this.detail = detail;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
this.streams = streams.splice ? streams.sort() : [streams];
|
this.streams = streams.splice ? streams.sort() : [streams];
|
||||||
|
this.cover = cover;
|
||||||
|
this.on_air = on_air;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound.prototype = {
|
Sound.prototype = {
|
||||||
|
@ -30,25 +32,60 @@ Sound.prototype = {
|
||||||
detail: '',
|
detail: '',
|
||||||
streams: undefined,
|
streams: undefined,
|
||||||
duration: undefined,
|
duration: undefined,
|
||||||
on_air_url: undefined,
|
cover: undefined,
|
||||||
|
on_air: false,
|
||||||
|
|
||||||
item: undefined,
|
item: undefined,
|
||||||
|
|
||||||
get seekable() {
|
get seekable() {
|
||||||
return this.duration != undefined;
|
return this.duration != undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_item: function(playlist, base_item) {
|
||||||
|
if(this.item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = base_item.cloneNode(true);
|
||||||
|
item.removeAttribute('style');
|
||||||
|
|
||||||
|
item.querySelector('.title').innerHTML = this.title;
|
||||||
|
if(this.seekable)
|
||||||
|
item.querySelector('.duration').innerHTML =
|
||||||
|
duration_str(this.duration);
|
||||||
|
if(this.detail)
|
||||||
|
item.querySelector('.detail').href = this.detail;
|
||||||
|
if(playlist.player.show_cover && this.cover)
|
||||||
|
item.querySelector('img.play').src = this.cover;
|
||||||
|
|
||||||
|
item.sound = this;
|
||||||
|
this.item = item;
|
||||||
|
|
||||||
|
// events
|
||||||
|
var self = this;
|
||||||
|
item.querySelector('.action.remove').addEventListener(
|
||||||
|
'click', function(event) { playlist.remove(self); }, false
|
||||||
|
);
|
||||||
|
|
||||||
|
item.addEventListener('click', function(event) {
|
||||||
|
if(event.target.className.indexOf('action') != -1)
|
||||||
|
return;
|
||||||
|
playlist.select(self, true)
|
||||||
|
}, false);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function PlayerPlaylist(player) {
|
function Playlist(player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.playlist = player.player.querySelector('.playlist');
|
this.playlist = player.player.querySelector('.playlist');
|
||||||
this.item_ = player.player.querySelector('.playlist .item');
|
this.item_ = player.player.querySelector('.playlist .item');
|
||||||
this.sounds = []
|
this.sounds = []
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerPlaylist.prototype = {
|
Playlist.prototype = {
|
||||||
|
on_air: undefined,
|
||||||
sounds: undefined,
|
sounds: undefined,
|
||||||
|
sound: undefined,
|
||||||
|
|
||||||
/// Find a sound by its streams, and return it if found
|
/// Find a sound by its streams, and return it if found
|
||||||
find: function(streams) {
|
find: function(streams) {
|
||||||
|
@ -71,30 +108,12 @@ PlayerPlaylist.prototype = {
|
||||||
if(sound_)
|
if(sound_)
|
||||||
return sound_;
|
return sound_;
|
||||||
|
|
||||||
var item = this.item_.cloneNode(true);
|
if(sound.on_air)
|
||||||
item.removeAttribute('style');
|
this.on_air = sound;
|
||||||
|
|
||||||
item.querySelector('.title').innerHTML = sound.title;
|
sound.make_item(this, this.item_);
|
||||||
if(sound.seekable)
|
(container || this.playlist).appendChild(sound.item);
|
||||||
item.querySelector('.duration').innerHTML =
|
|
||||||
duration_str(sound.duration);
|
|
||||||
if(sound.detail)
|
|
||||||
item.querySelector('.detail').href = sound.detail;
|
|
||||||
|
|
||||||
item.sound = sound;
|
|
||||||
sound.item = item;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
item.querySelector('.action.remove').addEventListener(
|
|
||||||
'click', function(event) { self.remove(sound); }, false
|
|
||||||
);
|
|
||||||
item.addEventListener('click', function(event) {
|
|
||||||
if(event.target.className.indexOf('action') != -1)
|
|
||||||
return;
|
|
||||||
self.player.select(sound, true)
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
(container || this.playlist).appendChild(item);
|
|
||||||
this.sounds.push(sound);
|
this.sounds.push(sound);
|
||||||
this.save();
|
this.save();
|
||||||
return sound;
|
return sound;
|
||||||
|
@ -106,8 +125,61 @@ PlayerPlaylist.prototype = {
|
||||||
this.sounds.splice(index,1);
|
this.sounds.splice(index,1);
|
||||||
this.playlist.removeChild(sound.item);
|
this.playlist.removeChild(sound.item);
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
|
this.player.stop()
|
||||||
|
this.next(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
select: function(sound, play = true) {
|
||||||
|
this.player.playlist = this;
|
||||||
|
if(this.sound == sound) {
|
||||||
|
if(play)
|
||||||
|
this.player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.sound)
|
||||||
|
this.unselect(this.sound);
|
||||||
|
this.sound = sound;
|
||||||
|
|
||||||
|
// audio
|
||||||
|
this.player.load_sound(this.sound);
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
var container = this.player.player;
|
||||||
|
sound.item.setAttribute('selected', 'true');
|
||||||
|
|
||||||
|
if(!sound.on_air)
|
||||||
|
sound.item.querySelector('.content').insertBefore(
|
||||||
|
this.player.progress.item,
|
||||||
|
sound.item.querySelector('.content .duration')
|
||||||
|
)
|
||||||
|
|
||||||
|
if(sound.seekable)
|
||||||
|
container.setAttribute('seekable', 'true');
|
||||||
|
else
|
||||||
|
container.removeAttribute('seekable');
|
||||||
|
|
||||||
|
// play
|
||||||
|
if(play)
|
||||||
|
this.player.play();
|
||||||
|
},
|
||||||
|
|
||||||
|
unselect: function(sound) {
|
||||||
|
sound.item.removeAttribute('selected');
|
||||||
|
},
|
||||||
|
|
||||||
|
next: function(play = true) {
|
||||||
|
var index = this.sounds.indexOf(this.sound);
|
||||||
|
if(index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if(index < this.sounds.length)
|
||||||
|
this.select(this.sounds[index]);
|
||||||
|
},
|
||||||
|
|
||||||
|
// storage
|
||||||
save: function() {
|
save: function() {
|
||||||
var list = [];
|
var list = [];
|
||||||
for(var i in this.sounds) {
|
for(var i in this.sounds) {
|
||||||
|
@ -124,7 +196,7 @@ PlayerPlaylist.prototype = {
|
||||||
for(var i in list) {
|
for(var i in list) {
|
||||||
var sound = list[i];
|
var sound = list[i];
|
||||||
sound = new Sound(sound.title, sound.detail, sound.duration,
|
sound = new Sound(sound.title, sound.detail, sound.duration,
|
||||||
sound.streams)
|
sound.streams, sound.cover, sound.on_air)
|
||||||
this.add(sound, container)
|
this.add(sound, container)
|
||||||
}
|
}
|
||||||
this.playlist.appendChild(container);
|
this.playlist.appendChild(container);
|
||||||
|
@ -132,53 +204,67 @@ PlayerPlaylist.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Player(id) {
|
function Player(id, on_air_url, show_cover) {
|
||||||
this.store = new Store('player');
|
this.id = id;
|
||||||
|
this.on_air_url = on_air_url;
|
||||||
|
this.show_cover = show_cover;
|
||||||
|
|
||||||
|
this.store = new Store('player_' + id);
|
||||||
|
|
||||||
// html sounds
|
// html sounds
|
||||||
this.player = document.getElementById(id);
|
this.player = document.getElementById(id);
|
||||||
this.audio = this.player.querySelector('audio');
|
this.audio = this.player.querySelector('audio');
|
||||||
this.on_air = this.player.querySelector('.on_air');
|
this.on_air = this.player.querySelector('.on_air');
|
||||||
|
this.progress = {
|
||||||
|
item: this.player.querySelector('.controls .progress'),
|
||||||
|
bar: this.player.querySelector('.controls .progress progress'),
|
||||||
|
duration: this.player.querySelector('.controls .progress .duration')
|
||||||
|
}
|
||||||
|
console.log(this.progress)
|
||||||
|
|
||||||
this.controls = {
|
this.controls = {
|
||||||
duration: this.player.querySelector('.controls .duration'),
|
|
||||||
progress: this.player.querySelector('progress'),
|
|
||||||
single: this.player.querySelector('input.single'),
|
single: this.player.querySelector('input.single'),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playlist = new PlayerPlaylist(this);
|
this.playlist = new Playlist(this);
|
||||||
this.playlist.load();
|
this.playlist.load();
|
||||||
|
|
||||||
this.init_events();
|
this.init_events();
|
||||||
this.load();
|
this.load();
|
||||||
|
|
||||||
|
this.update_on_air();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player.prototype = {
|
Player.prototype = {
|
||||||
/// current item being played
|
/// current item being played
|
||||||
sound: undefined,
|
sound: undefined,
|
||||||
|
on_air_url: undefined,
|
||||||
|
|
||||||
init_events: function() {
|
init_events: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
function time_from_progress(event) {
|
function time_from_progress(event) {
|
||||||
bounding = self.controls.progress.getBoundingClientRect()
|
bounding = self.progress.bar.getBoundingClientRect()
|
||||||
offset = (event.clientX - bounding.left);
|
offset = (event.clientX - bounding.left);
|
||||||
return offset * self.audio.duration / bounding.width;
|
return offset * self.audio.duration / bounding.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_info() {
|
function update_info() {
|
||||||
var controls = self.controls;
|
var progress = self.progress;
|
||||||
|
var pos = self.audio.currentTime;
|
||||||
|
|
||||||
// progress
|
// progress
|
||||||
if(!self.sound || !self.sound.seekable ||
|
if(!self.sound || !self.sound.seekable ||
|
||||||
self.audio.duration == Infinity) {
|
!pos || self.audio.duration == Infinity)
|
||||||
controls.duration.innerHTML = '';
|
{
|
||||||
controls.progress.value = 0;
|
progress.duration.innerHTML = '';
|
||||||
|
progress.bar.value = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pos = self.audio.currentTime;
|
progress.bar.value = pos;
|
||||||
controls.progress.value = pos;
|
progress.bar.max = self.audio.duration;
|
||||||
controls.progress.max = self.audio.duration;
|
progress.duration.innerHTML = duration_str(pos);
|
||||||
controls.duration.innerHTML = duration_str(pos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
|
@ -207,7 +293,7 @@ Player.prototype = {
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// progress
|
// progress
|
||||||
progress = this.controls.progress;
|
progress = this.progress.bar;
|
||||||
progress.addEventListener('click', function(event) {
|
progress.addEventListener('click', function(event) {
|
||||||
player.audio.currentTime = time_from_progress(event);
|
player.audio.currentTime = time_from_progress(event);
|
||||||
}, false);
|
}, false);
|
||||||
|
@ -215,43 +301,55 @@ Player.prototype = {
|
||||||
progress.addEventListener('mouseout', update_info, false);
|
progress.addEventListener('mouseout', update_info, false);
|
||||||
|
|
||||||
progress.addEventListener('mousemove', function(event) {
|
progress.addEventListener('mousemove', function(event) {
|
||||||
if(self.audio.duration == Infinity)
|
if(self.audio.duration == Infinity || isNaN(self.audio.duration))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var pos = time_from_progress(event);
|
var pos = time_from_progress(event);
|
||||||
self.controls.duration.innerHTML = duration_str(pos);
|
self.progress.duration.innerHTML = duration_str(pos);
|
||||||
}, false);
|
}, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
update_on_air: function(url) {
|
update_on_air: function() {
|
||||||
if(!url) {
|
if(!this.on_air_url)
|
||||||
// TODO HERE
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
window.setTimeout(function() {
|
||||||
|
self.update_on_air();
|
||||||
|
}, 60*1000);
|
||||||
|
|
||||||
|
if(!this.playlist.on_air)
|
||||||
|
return;
|
||||||
|
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.open('GET', url, true);
|
req.open('GET', this.on_air_url, true);
|
||||||
req.onreadystatechange = function() {
|
req.onreadystatechange = function() {
|
||||||
if(req.readyState != 4 || (req.status != 200 && req.status != 0))
|
if(req.readyState != 4 || (req.status != 200 &&
|
||||||
|
req.status != 0))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var data = JSON.parse(req.responseText)
|
var data = JSON.parse(req.responseText)
|
||||||
if(data.type == 'track') {
|
if(data.type == 'track')
|
||||||
self.on_air.querySelector('.info').innerHTML = '♫';
|
data = {
|
||||||
self.on_air.querySelector('.title') =
|
title: '♫' + (data.artist ? data.artist + ' — ' : '') +
|
||||||
(data.artist || '') + ' — ' + (data.title);
|
data.title,
|
||||||
self.on_air.querySelector('.url').removeAttribute('href');
|
url: ''
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
self.on_air.querySelector('.info').innerHTML = '';
|
data = {
|
||||||
self.on_air.querySelector('.title').innerHTML = data.title;
|
title: data.title,
|
||||||
self.on_air.querySelector('.url').setAttribute('href', data.url);
|
info: '',
|
||||||
}
|
url: data.url
|
||||||
|
}
|
||||||
|
|
||||||
if(timeout)
|
var on_air = self.playlist.on_air;
|
||||||
window.setTimeout(function() {
|
on_air = on_air.item.querySelector('.content');
|
||||||
self.update_on_air(url);
|
|
||||||
}, 60*1000);
|
if(data.url)
|
||||||
|
on_air.innerHTML =
|
||||||
|
'<a href="' + data.url + '">' + data.title + '</a>';
|
||||||
|
else
|
||||||
|
on_air.innerHTML = data.title;
|
||||||
};
|
};
|
||||||
req.send();
|
req.send();
|
||||||
},
|
},
|
||||||
|
@ -263,78 +361,44 @@ Player.prototype = {
|
||||||
this.audio.pause();
|
this.audio.pause();
|
||||||
},
|
},
|
||||||
|
|
||||||
unselect: function(sound) {
|
|
||||||
sound.item.removeAttribute('selected');
|
|
||||||
},
|
|
||||||
|
|
||||||
__mime_type: function(path) {
|
__mime_type: function(path) {
|
||||||
ext = path.substr(path.lastIndexOf('.')+1);
|
ext = path.substr(path.lastIndexOf('.')+1);
|
||||||
return 'audio/' + ext;
|
return 'audio/' + ext;
|
||||||
},
|
},
|
||||||
|
|
||||||
select: function(sound, play = true) {
|
load_sound: function(sound) {
|
||||||
if(this.sound == sound) {
|
var audio = this.audio;
|
||||||
if(play)
|
audio.pause();
|
||||||
this.play();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.sound)
|
var sources = audio.querySelectorAll('source');
|
||||||
this.unselect(this.sound);
|
for(var i = 0; i < sources.length; i++)
|
||||||
|
audio.removeChild(sources[i]);
|
||||||
this.audio.pause();
|
|
||||||
|
|
||||||
// streams as <source>
|
|
||||||
var sources = this.audio.querySelectorAll('source');
|
|
||||||
for(var i = 0; i < sources.length; i++) {
|
|
||||||
this.audio.removeChild(sources[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
streams = sound.streams;
|
streams = sound.streams;
|
||||||
for(var i = 0; i < streams.length; i++) {
|
for(var i = 0; i < streams.length; i++) {
|
||||||
var source = document.createElement('source');
|
var source = document.createElement('source');
|
||||||
source.src = streams[i];
|
source.src = streams[i];
|
||||||
source.type = this.__mime_type(source.src);
|
source.type = this.__mime_type(source.src);
|
||||||
this.audio.appendChild(source);
|
audio.appendChild(source);
|
||||||
}
|
}
|
||||||
this.audio.load();
|
audio.load();
|
||||||
|
|
||||||
// attributes
|
|
||||||
this.sound = sound;
|
|
||||||
sound.item.setAttribute('selected', 'true');
|
|
||||||
|
|
||||||
if(sound.seekable)
|
|
||||||
this.player.setAttribute('seekable', 'true');
|
|
||||||
else
|
|
||||||
this.player.removeAttribute('seekable');
|
|
||||||
|
|
||||||
// play
|
|
||||||
if(play)
|
|
||||||
this.play();
|
|
||||||
},
|
|
||||||
|
|
||||||
next: function() {
|
|
||||||
var index = this.playlist.sounds.indexOf(this.sound);
|
|
||||||
if(index < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if(index < this.playlist.sounds.length)
|
|
||||||
this.select(this.playlist.sounds[index], true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save: function() {
|
save: function() {
|
||||||
|
// TODO: move stored sound into playlist
|
||||||
this.store.set('player', {
|
this.store.set('player', {
|
||||||
single: this.controls.single.checked,
|
single: this.controls.single.checked,
|
||||||
sound: this.sound && this.sound.streams,
|
sound: this.playlist.sound && this.playlist.sound.streams,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
var data = this.store.get('player');
|
var data = this.store.get('player');
|
||||||
|
if(!data)
|
||||||
|
return;
|
||||||
this.controls.single.checked = data.single;
|
this.controls.single.checked = data.single;
|
||||||
if(data.sound)
|
if(data.sound)
|
||||||
this.sound = this.playlist.find(data.sound);
|
this.playlist.sound = this.playlist.find(data.sound);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,8 +67,13 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
{% render_sections position="footer" %}
|
{% render_sections position="footer" %}
|
||||||
|
<div class="small">Propulsed by
|
||||||
|
<a href="https://github.com/bkfox/aircox">Aircox</a>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -34,16 +34,26 @@
|
||||||
{% if podcasts %}
|
{% if podcasts %}
|
||||||
<section class="podcasts list">
|
<section class="podcasts list">
|
||||||
<h2>{% trans "Podcasts" %}</h2>
|
<h2>{% trans "Podcasts" %}</h2>
|
||||||
{% for item in podcasts %}
|
<div id="player_diff_{{ page.id }}" class="player">
|
||||||
{% include 'cms/snippets/sound_list_item.html' %}
|
{% include 'cms/snippets/player.html' %}
|
||||||
{% endfor %}
|
|
||||||
|
<script>
|
||||||
|
var podcasts = new Player('player_diff_{{ page.id }}', undefined, true)
|
||||||
|
{% for item in podcasts %}
|
||||||
|
podcasts.playlist.add(new Sound(
|
||||||
|
title='{{ item.name|escape }}',
|
||||||
|
detail='{{ item.detail_url }}',
|
||||||
|
duration={{ item.duration|date:"H*3600+i*60+s" }},
|
||||||
|
streams='{{ item.url }}',
|
||||||
|
{% if page and page.cover %}cover='{{ page.icon }}',{% endif %}
|
||||||
|
undefined
|
||||||
|
));
|
||||||
|
{% endfor %}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{# TODO: podcasts #}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,23 +6,30 @@
|
||||||
|
|
||||||
{% load aircox_cms %}
|
{% load aircox_cms %}
|
||||||
|
|
||||||
|
{% if not object_list %}
|
||||||
|
{% block title %}
|
||||||
|
<h1 class="detail_title">{{ page.title }}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if object_list %}
|
{% if object_list %}
|
||||||
{# list view #}
|
{# list view #}
|
||||||
<div class="body summary">
|
<section class="body summary">
|
||||||
{{ page.summary }}
|
{{ page.summary }}
|
||||||
<a href="?" class="go_back">{% trans "Go back to the publication" %}</a>
|
<a href="?" class="go_back">{% trans "Go back to the publication" %}</a>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
{% with list_paginator=paginator %}
|
{% with list_paginator=paginator %}
|
||||||
{% include "cms/snippets/list.html" %}
|
{% include "cms/snippets/list.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{# detail view #}
|
{# detail view #}
|
||||||
|
{% if page.cover %}
|
||||||
|
{% image page.cover max-600x480 class="detail_cover cover" height="" width="" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% if page.cover %}
|
|
||||||
{% image page.cover max-600x480 class="cover" height="" width="" %}
|
|
||||||
{% endif %}
|
|
||||||
<section class="body">
|
<section class="body">
|
||||||
{{ page.body|richtext}}
|
{{ page.body|richtext}}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% with url=url url_text=self.url_text %}
|
||||||
{% include "cms/snippets/list.html" %}
|
{% include "cms/snippets/list.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
{% if url %}
|
|
||||||
<nav><a href="{{ url }}">{{ self.url_text }}</a></nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,59 +1,24 @@
|
||||||
{% extends 'cms/sections/section_item.html' %}
|
{% extends 'cms/sections/section_item.html' %}
|
||||||
|
|
||||||
{% load staticfiles %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<div id="player" class="player">
|
<div id="player" class="player">
|
||||||
<audio preload="metadata">
|
{% include "cms/snippets/player.html" %}
|
||||||
{% trans "Your browser does not support the <code>audio</code> element." %}
|
|
||||||
{% for stream in streams %}
|
|
||||||
<source src="{{ stream }}" />
|
|
||||||
{% endfor %}
|
|
||||||
</audio>
|
|
||||||
|
|
||||||
<div class="controls flex_row">
|
|
||||||
<progress class="flex_item" value="0" max="1"></progress>
|
|
||||||
<span class="info duration"></span>
|
|
||||||
|
|
||||||
<input type="checkbox" class="single" id="player_single_mode">
|
|
||||||
<label for="player_single_mode" class="info" title="{% trans "single mode" %}">↻</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="playlist">
|
|
||||||
<li class='item list_item flex_row' style="display: none;">
|
|
||||||
<div class="button">
|
|
||||||
<img src="{% static "cms/images/play.png" %}" class="play"
|
|
||||||
title="{% trans "play" %}" />
|
|
||||||
<img src="{% static "cms/images/pause.png" %}" class="pause"
|
|
||||||
title="{% trans "pause" %}" />
|
|
||||||
<img src="{% static "cms/images/loading.png" %}" class="loading"
|
|
||||||
title="{% trans "loading..." %}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="title flex_item">{{ self.live_title }}</h3>
|
|
||||||
<div class="actions">
|
|
||||||
<a class="action detail" title="{% trans "more informations" %}">➔</a>
|
|
||||||
<a class="action remove" title="{% trans "remove this sound" %}">✖</a>
|
|
||||||
</div>
|
|
||||||
<span class="info duration"></span>
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var player = new Player('player');
|
var player = new Player('player', '{% url 'controllers.on_air' %}');
|
||||||
var sound = player.playlist.add(new Sound('{{ self.live_title }}', '', undefined,
|
var sound = player.playlist.add(
|
||||||
streams=[
|
new Sound(
|
||||||
{% for stream in streams %}'{{ stream }}',{% endfor %}
|
'{{ self.live_title }}',
|
||||||
]), on_air_url = '{% url 'controllers.on_air' %}');
|
'', undefined,
|
||||||
player.select(sound, false);
|
streams=[ {% for stream in streams %}'{{ stream }}',{% endfor %} ],
|
||||||
|
cover = undefined,
|
||||||
|
on_air = true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
sound.item.className += ' live';
|
||||||
|
player.playlist.select(sound, false);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends "cms/sections/section_item.html" %}
|
{% extends "cms/sections/section_item.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<div class="author">
|
<div class="author">
|
||||||
{% if page.publish_as %}
|
{% if page.publish_as %}
|
||||||
|
@ -24,4 +25,5 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ Options:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</nav>
|
</nav>
|
||||||
|
{% elif url and url_text %}
|
||||||
|
<nav><a href="{{ url }}">{{ url_text }}</a></nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
45
cms/templates/cms/snippets/player.html
Normal file
45
cms/templates/cms/snippets/player.html
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<audio preload="metadata">
|
||||||
|
{% trans "Your browser does not support the <code>audio</code> element." %}
|
||||||
|
{% for stream in streams %}
|
||||||
|
<source src="{{ stream }}" />
|
||||||
|
{% endfor %}
|
||||||
|
</audio>
|
||||||
|
|
||||||
|
<div class="playlist">
|
||||||
|
<li class='item list_item flex_row' style="display: none;">
|
||||||
|
<div class="button">
|
||||||
|
<img src="{% static "cms/images/play.png" %}" class="play"
|
||||||
|
title="{% trans "play" %}" />
|
||||||
|
<img src="{% static "cms/images/pause.png" %}" class="pause"
|
||||||
|
title="{% trans "pause" %}" />
|
||||||
|
<img src="{% static "cms/images/loading.png" %}" class="loading"
|
||||||
|
title="{% trans "loading..." %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex_item">
|
||||||
|
<h3 class="title flex_item">{{ self.live_title }}</h3>
|
||||||
|
<div class="content flex_row">
|
||||||
|
<span class="info duration flex_item"></span>
|
||||||
|
<span class="actions">
|
||||||
|
<a class="action detail" title="{% trans "more informations" %}">➔</a>
|
||||||
|
<a class="action remove" title="{% trans "remove this sound" %}">✖</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<span class="progress">
|
||||||
|
<span class="info duration"></span>
|
||||||
|
<progress class="flex_item progress" value="0" max="1"></progress>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input type="checkbox" class="single" id="player_single_mode">
|
||||||
|
<label for="player_single_mode" class="info"
|
||||||
|
title="{% trans "enable and disable single mode" %}">↻</label>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,22 +1,37 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{# TODO: complete archive podcast -> info #}
|
{# TODO: complete archive podcast -> info #}
|
||||||
<div class="list_item sound">
|
<script>
|
||||||
<a onclick="player.select(player.playlist.add(new Sound(
|
function add_sound_{{ item.id }}(event) {
|
||||||
|
var sound = new Sound(
|
||||||
title='{{ item.name|escape }}',
|
title='{{ item.name|escape }}',
|
||||||
detail='{{ item.detail_url }}',
|
detail='{{ item.detail_url }}',
|
||||||
duration={{ item.duration|date:"H*3600+i*60+s" }},
|
duration={{ item.duration|date:"H*3600+i*60+s" }},
|
||||||
streams='{{ item.url }}')));" class="flex_row">
|
streams='{{ item.url }}',
|
||||||
|
{% if page and page.cover %}cover='{{ page.icon }}'{% endif %}
|
||||||
|
);
|
||||||
|
|
||||||
|
sound = player.playlist.add(sound);
|
||||||
|
|
||||||
|
if(event.target.dataset.action != 'add')
|
||||||
|
player.select(sound, true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a class="list_item sound flex_row" onclick="add_sound_{{ item.id }}(event)">
|
||||||
<img src="{% static "cms/images/listen.png" %}" class="icon"/>
|
<img src="{% static "cms/images/listen.png" %}" class="icon"/>
|
||||||
<h3 class="flex_item">{{ item.name }}</h3>
|
<h3 class="flex_item">{{ item.name }}</h3>
|
||||||
|
|
||||||
<span class="info">
|
<time class="info">
|
||||||
{% if item.duration.hour > 0 %}
|
{% if item.duration.hour > 0 %}
|
||||||
{{ item.duration|date:'H:i:s' }}
|
{{ item.duration|date:'H:i:s' }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ item.duration|date:'i:s' }}
|
{{ item.duration|date:'i:s' }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</time>
|
||||||
</a>
|
|
||||||
</div>
|
<img src="{% static "cms/images/add.png" %}" class="icon"
|
||||||
|
data-action='add' alt="{% trans "add this sound to the playlist" %}"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,84 @@
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
from wagtail.wagtailcore import hooks
|
from wagtail.wagtailcore import hooks
|
||||||
from wagtail.wagtailadmin.menu import MenuItem, Menu, SubmenuMenuItem
|
from wagtail.wagtailadmin.menu import MenuItem, Menu, SubmenuMenuItem
|
||||||
|
|
||||||
|
from wagtail.contrib.modeladmin.options import \
|
||||||
|
ModelAdmin, ModelAdminGroup, modeladmin_register
|
||||||
|
|
||||||
|
|
||||||
import aircox.programs.models as programs
|
import aircox.programs.models as programs
|
||||||
|
import aircox.cms.models as models
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramAdmin(ModelAdmin):
|
||||||
|
model = programs.Program
|
||||||
|
menu_label = _('Programs')
|
||||||
|
menu_icon = 'pick'
|
||||||
|
menu_order = 200
|
||||||
|
list_display = ('name', 'active')
|
||||||
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
class DiffusionAdmin(ModelAdmin):
|
||||||
|
model = programs.Diffusion
|
||||||
|
menu_label = _('Diffusions')
|
||||||
|
menu_icon = 'date'
|
||||||
|
menu_order = 200
|
||||||
|
list_display = ('program', 'start', 'end', 'frequency', 'initial')
|
||||||
|
list_filter = ('frequency', 'start', 'program')
|
||||||
|
|
||||||
|
class ScheduleAdmin(ModelAdmin):
|
||||||
|
model = programs.Schedule
|
||||||
|
menu_label = _('Schedules')
|
||||||
|
menu_icon = 'time'
|
||||||
|
menu_order = 200
|
||||||
|
list_display = ('program', 'frequency', 'duration', 'initial')
|
||||||
|
list_filter = ('frequency', 'date', 'duration', 'program')
|
||||||
|
|
||||||
|
class StreamAdmin(ModelAdmin):
|
||||||
|
model = programs.Stream
|
||||||
|
menu_label = _('Streams')
|
||||||
|
menu_icon = 'time'
|
||||||
|
menu_order = 200
|
||||||
|
list_display = ('program', 'delay', 'begin', 'end')
|
||||||
|
list_filter = ('program', 'delay', 'begin', 'end')
|
||||||
|
|
||||||
|
class AdvancedAdminGroup(ModelAdminGroup):
|
||||||
|
menu_label = _("Advanced")
|
||||||
|
menu_icon = 'plus-inverse'
|
||||||
|
items = (ProgramAdmin, DiffusionAdmin, ScheduleAdmin, StreamAdmin)
|
||||||
|
|
||||||
|
modeladmin_register(AdvancedAdminGroup)
|
||||||
|
|
||||||
|
|
||||||
|
class SoundAdmin(ModelAdmin):
|
||||||
|
model = programs.Sound
|
||||||
|
menu_label = _('Sounds')
|
||||||
|
menu_icon = 'media'
|
||||||
|
menu_order = 350
|
||||||
|
list_display = ('name', 'duration', 'type', 'path', 'good_quality', 'public')
|
||||||
|
list_filter = ('type', 'good_quality', 'public')
|
||||||
|
search_fields = ('name', 'path')
|
||||||
|
|
||||||
|
modeladmin_register(SoundAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
@hooks.register('insert_editor_css')
|
||||||
|
def editor_css():
|
||||||
|
return format_html(
|
||||||
|
'<link rel="stylesheet" href="{}">',
|
||||||
|
static('cms/css/cms.css')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GenericMenu(Menu):
|
class GenericMenu(Menu):
|
||||||
last_time = None
|
page_model = models.Publication
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__('')
|
super().__init__('')
|
||||||
|
@ -19,40 +89,47 @@ class GenericMenu(Menu):
|
||||||
def get_title(self, item):
|
def get_title(self, item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_parent_page(self, item):
|
def get_parent(self, item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_page_url(self, item):
|
def get_page_url(self, page_model, item):
|
||||||
if item.page.count():
|
if item.page.count():
|
||||||
return reverse('wagtailadmin_pages:edit', args=[item.page.first().id])
|
return reverse('wagtailadmin_pages:edit', args=[item.page.first().id])
|
||||||
parent_page = self.get_parent_page(item)
|
|
||||||
|
parent_page = self.get_parent(item)
|
||||||
if not parent_page:
|
if not parent_page:
|
||||||
return ''
|
return ''
|
||||||
return reverse('wagtailadmin_pages:add_subpage', args=[parent_page.id])
|
|
||||||
|
return reverse(
|
||||||
|
'wagtailadmin_pages:add', args= [
|
||||||
|
page_model._meta.app_label, page_model._meta.model_name,
|
||||||
|
parent_page.id
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def registered_menu_items(self):
|
def registered_menu_items(self):
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
last_max = now - tz.timedelta(minutes = 10)
|
last_max = now - tz.timedelta(minutes = 10)
|
||||||
|
|
||||||
if self._registered_menu_items is None or self.last_time < last_max:
|
qs = self.get_queryset()
|
||||||
qs = self.get_queryset()
|
return [
|
||||||
self._registered_menu_items = [
|
MenuItem(self.get_title(x), self.get_page_url(self.page_model, x))
|
||||||
MenuItem(self.get_title(x), self.get_page_url(x))
|
for x in qs
|
||||||
for x in qs
|
]
|
||||||
]
|
|
||||||
self.last_time = now
|
|
||||||
return self._registered_menu_items
|
|
||||||
|
|
||||||
|
|
||||||
class DiffusionsMenu(GenericMenu):
|
class DiffusionsMenu(GenericMenu):
|
||||||
"""
|
"""
|
||||||
Menu to display diffusions of today
|
Menu to display diffusions of today
|
||||||
"""
|
"""
|
||||||
|
page_model = models.DiffusionPage
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return programs.Diffusion.objects.filter(
|
return programs.Diffusion.objects.filter(
|
||||||
type = programs.Diffusion.Type.normal,
|
type = programs.Diffusion.Type.normal,
|
||||||
start__contains = tz.now().date(),
|
start__contains = tz.now().date(),
|
||||||
|
initial__isnull = True,
|
||||||
).order_by('start')
|
).order_by('start')
|
||||||
|
|
||||||
def get_title(self, item):
|
def get_title(self, item):
|
||||||
|
@ -74,6 +151,8 @@ class ProgramsMenu(GenericMenu):
|
||||||
"""
|
"""
|
||||||
Menu to display all active programs.
|
Menu to display all active programs.
|
||||||
"""
|
"""
|
||||||
|
page_model = models.DiffusionPage
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return programs.Program.objects \
|
return programs.Program.objects \
|
||||||
.filter(active = True, page__isnull = False) \
|
.filter(active = True, page__isnull = False) \
|
||||||
|
@ -92,7 +171,6 @@ class ProgramsMenu(GenericMenu):
|
||||||
return settings.default_program_parent_page
|
return settings.default_program_parent_page
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('register_admin_menu_item')
|
@hooks.register('register_admin_menu_item')
|
||||||
def register_programs_menu_item():
|
def register_programs_menu_item():
|
||||||
return SubmenuMenuItem(
|
return SubmenuMenuItem(
|
||||||
|
@ -101,4 +179,3 @@ def register_programs_menu_item():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ def on_air(request):
|
||||||
|
|
||||||
last = {
|
last = {
|
||||||
'type': 'diffusion',
|
'type': 'diffusion',
|
||||||
'title': publication.title if publication else last.program.name,
|
'title': last.program.name,
|
||||||
'date': last.start,
|
'date': last.start,
|
||||||
'url': publication.specific.url if publication else None,
|
'url': publication.specific.url if publication else None,
|
||||||
}
|
}
|
||||||
|
|
2
notes.md
2
notes.md
|
@ -26,10 +26,10 @@ This file is used as a reminder, can be used as crappy documentation too.
|
||||||
|
|
||||||
- controllers :
|
- controllers :
|
||||||
- models to template -> note
|
- models to template -> note
|
||||||
|
- input stream
|
||||||
- streamed program disable -> remote control on liquidsoap
|
- streamed program disable -> remote control on liquidsoap
|
||||||
- tests:
|
- tests:
|
||||||
- monitor
|
- monitor
|
||||||
- check when a played sound has a temp blank
|
|
||||||
- config generation and sound diffusion
|
- config generation and sound diffusion
|
||||||
|
|
||||||
- cms:
|
- cms:
|
||||||
|
|
|
@ -268,38 +268,6 @@ class Sound(Nameable):
|
||||||
verbose_name_plural = _('Sounds')
|
verbose_name_plural = _('Sounds')
|
||||||
|
|
||||||
|
|
||||||
class Stream(models.Model):
|
|
||||||
"""
|
|
||||||
When there are no program scheduled, it is possible to play sounds
|
|
||||||
in order to avoid blanks. A Stream is a Program that plays this role,
|
|
||||||
and whose linked to a Stream.
|
|
||||||
|
|
||||||
All sounds that are marked as good and that are under the related
|
|
||||||
program's archive dir are elligible for the sound's selection.
|
|
||||||
"""
|
|
||||||
program = models.ForeignKey(
|
|
||||||
'Program',
|
|
||||||
verbose_name = _('related program'),
|
|
||||||
)
|
|
||||||
delay = models.TimeField(
|
|
||||||
_('delay'),
|
|
||||||
blank = True, null = True,
|
|
||||||
help_text = _('delay between two sound plays')
|
|
||||||
)
|
|
||||||
begin = models.TimeField(
|
|
||||||
_('begin'),
|
|
||||||
blank = True, null = True,
|
|
||||||
help_text = _('used to define a time range this stream is'
|
|
||||||
'played')
|
|
||||||
)
|
|
||||||
end = models.TimeField(
|
|
||||||
_('end'),
|
|
||||||
blank = True, null = True,
|
|
||||||
help_text = _('used to define a time range this stream is'
|
|
||||||
'played')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Schedule(models.Model):
|
class Schedule(models.Model):
|
||||||
"""
|
"""
|
||||||
A Schedule defines time slots of programs' diffusions. It can be an initial
|
A Schedule defines time slots of programs' diffusions. It can be an initial
|
||||||
|
@ -636,6 +604,38 @@ class DiffusionManager(models.Manager):
|
||||||
).order_by('start')
|
).order_by('start')
|
||||||
|
|
||||||
|
|
||||||
|
class Stream(models.Model):
|
||||||
|
"""
|
||||||
|
When there are no program scheduled, it is possible to play sounds
|
||||||
|
in order to avoid blanks. A Stream is a Program that plays this role,
|
||||||
|
and whose linked to a Stream.
|
||||||
|
|
||||||
|
All sounds that are marked as good and that are under the related
|
||||||
|
program's archive dir are elligible for the sound's selection.
|
||||||
|
"""
|
||||||
|
program = models.ForeignKey(
|
||||||
|
'Program',
|
||||||
|
verbose_name = _('related program'),
|
||||||
|
)
|
||||||
|
delay = models.TimeField(
|
||||||
|
_('delay'),
|
||||||
|
blank = True, null = True,
|
||||||
|
help_text = _('delay between two sound plays')
|
||||||
|
)
|
||||||
|
begin = models.TimeField(
|
||||||
|
_('begin'),
|
||||||
|
blank = True, null = True,
|
||||||
|
help_text = _('used to define a time range this stream is'
|
||||||
|
'played')
|
||||||
|
)
|
||||||
|
end = models.TimeField(
|
||||||
|
_('end'),
|
||||||
|
blank = True, null = True,
|
||||||
|
help_text = _('used to define a time range this stream is'
|
||||||
|
'played')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Diffusion(models.Model):
|
class Diffusion(models.Model):
|
||||||
"""
|
"""
|
||||||
A Diffusion is an occurrence of a Program that is scheduled on the
|
A Diffusion is an occurrence of a Program that is scheduled on the
|
||||||
|
|
Loading…
Reference in New Issue
Block a user