forked from rc/aircox
519 lines
14 KiB
HTML
519 lines
14 KiB
HTML
{% extends 'aircox/cms/list.html' %}
|
|
|
|
{% load staticfiles %}
|
|
{% load i18n %}
|
|
|
|
{% block header %}
|
|
<style>
|
|
.player-box {
|
|
padding-top: 0.2em;
|
|
}
|
|
|
|
.player-box * {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.player-box h3, #player h2 {
|
|
display: inline-block;
|
|
font-size: 0.9em;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.player-button {
|
|
display: inline-block;
|
|
width: 1.5em;
|
|
height: 1.5em;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
margin-right: 0.4em;
|
|
text-align: center;
|
|
}
|
|
|
|
|
|
.player-button:after {
|
|
content: '▶';
|
|
}
|
|
|
|
#player[state="playing"] .player-button:after {
|
|
content: '▮▮';
|
|
}
|
|
|
|
#player[state="stalled"] .player-button:after {
|
|
content: '...';
|
|
}
|
|
|
|
#player .on_air {
|
|
padding: 0.2em;
|
|
}
|
|
|
|
#player .on_air .title {
|
|
font-size: 0.8em;
|
|
display: inline;
|
|
}
|
|
|
|
#player .on_air a {
|
|
float: right;
|
|
}
|
|
|
|
|
|
.playlists {
|
|
}
|
|
|
|
.playlists ul:not([selected]) {
|
|
display: none;
|
|
}
|
|
|
|
.playlists nav a.close {
|
|
float: right;
|
|
}
|
|
|
|
.playlist {
|
|
margin: 0;
|
|
padding: 0;
|
|
height: 15em;
|
|
/*overflow-y: auto;*/
|
|
}
|
|
|
|
.playlist .item > *:not(.actions) {
|
|
display: inline;
|
|
margin: 0.2em;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.playlist .item .info {
|
|
float: right;
|
|
}
|
|
|
|
|
|
.playlist .actions {
|
|
float: right;
|
|
}
|
|
|
|
.playlist .actions a.action {
|
|
display: inline;
|
|
}
|
|
|
|
#playlist-live .actions,
|
|
#playlist-recents .actions a.action[action="remove"],
|
|
#playlist-marked .actions a.action[action="mark"],
|
|
.playlist .actions a.action[action="play"],
|
|
.playlist .actions a.url:not([href]),
|
|
.playlist .actions a.url[href=""] {
|
|
display: none;
|
|
}
|
|
|
|
.playlist .action[action="remove"] {
|
|
float: right;
|
|
}
|
|
</style>
|
|
<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;">
|
|
<h2 class="title"></h2>
|
|
<div class="info"></div>
|
|
<div class="actions">
|
|
<a class="url action" title="{% trans "more informations" %}">➔</a>
|
|
<a class="action" action="remove" title="{% trans "remove from the playlist" %}">✖</a>
|
|
</div>
|
|
</li>
|
|
<div class="player-box">
|
|
<div id="embed-player">
|
|
</div>
|
|
<div id="simple-player">
|
|
<audio preload="metadata">
|
|
Your browser does not support the <code>audio</code> element.
|
|
</audio>
|
|
|
|
<span class="player-button" onclick="player.play()"
|
|
title="{% trans "play/pause" %}"></span>
|
|
|
|
<h3 class="title"></h3>
|
|
</div>
|
|
</div>
|
|
<div class="playlists">
|
|
<nav>
|
|
<a onclick="player.select_playlist()" class="close"
|
|
title="{% trans "close" %}">✖</a>
|
|
<!-- single mode -->
|
|
</nav>
|
|
</div>
|
|
<div class='item on_air'>
|
|
<h2 class="title"></h2>
|
|
<a class="url">➔</a>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Create a Playlist:
|
|
// * name: name of the playlist, used for container id and storage
|
|
// * tab: text to put in the tab
|
|
// * items: list of items to append
|
|
// * store: store the playlist in localStorage
|
|
function Playlist(player, name, tab, items, store = false) {
|
|
this.player = player;
|
|
this.name = name;
|
|
this.store = store;
|
|
|
|
this.playlist = document.createElement('ul');
|
|
this.playlist.setAttribute('id', 'playlist-' + name );
|
|
this.playlist.className = 'playlist list';
|
|
|
|
var self = this;
|
|
this.tab = document.createElement('a');
|
|
this.tab.addEventListener('click', function(event) {
|
|
player.select_playlist(self);
|
|
event.preventDefault();
|
|
}, true);
|
|
this.tab.className = 'tab';
|
|
this.tab.innerHTML = tab;
|
|
|
|
player.playlists.appendChild(this.playlist);
|
|
player.playlists.querySelector('nav').appendChild(this.tab);
|
|
|
|
this.items = [];
|
|
if(store)
|
|
this.load()
|
|
if(items)
|
|
this.add_list(items);
|
|
}
|
|
|
|
Playlist.prototype = {
|
|
items: undefined,
|
|
|
|
/// find an item in playlist
|
|
find: function(item) {
|
|
return this.items.find(function(v) {
|
|
return v.stream == item.stream;
|
|
});
|
|
},
|
|
|
|
/// add an item to the playlist or container, if not in this playlist.
|
|
/// return the existing item or the newly created item.
|
|
add: function(item, container) {
|
|
var item_ = this.find(item);
|
|
if(item_)
|
|
return item_;
|
|
|
|
var player = this.player;
|
|
var elm = player.player.querySelector('.item').cloneNode(true);
|
|
elm.removeAttribute('style');
|
|
|
|
if(!container)
|
|
container = this.playlist;
|
|
if(container.childNodes.length)
|
|
container.insertBefore(elm, container.childNodes[0]);
|
|
else
|
|
container.appendChild(elm);
|
|
|
|
item.elm = elm;
|
|
item.playlist = this;
|
|
elm.item = item;
|
|
|
|
elm.querySelector('.title').innerHTML = item.title || '';
|
|
elm.querySelector('.url').href = item.url || '';
|
|
elm.querySelector('.info').innerHTML = item.info || '';
|
|
|
|
if(item.class)
|
|
elm.className += " " + item.class;
|
|
|
|
elm.addEventListener('click', function(event) {
|
|
if(event.currentTarget.tagName == 'A' ||
|
|
event.target.tagName == 'A')
|
|
return;
|
|
|
|
var item = event.currentTarget.item;
|
|
if(item.stream || item.embed)
|
|
player.select(item);
|
|
event.stopPropagation();
|
|
return true;
|
|
}, false);
|
|
|
|
if(item.embed || item.stream)
|
|
player.add_actions(item, elm);
|
|
this.items.push(item);
|
|
|
|
if(container == this.playlist && this.store)
|
|
this.save()
|
|
return item;
|
|
},
|
|
|
|
/// Add a list of items (optimized)
|
|
add_list: function (items) {
|
|
var container = document.createDocumentFragment();
|
|
for(var i = 0; i < items.length; i++)
|
|
this.add(items[i], container);
|
|
this.playlist.appendChild(container);
|
|
|
|
if(this.store)
|
|
this.save()
|
|
},
|
|
|
|
/// remove an item from the playlist
|
|
remove: function(item) {
|
|
for(var i = 0; i < this.items.length; i++) {
|
|
var item_ = this.items[i];
|
|
if(item_.stream != item.stream)
|
|
continue;
|
|
item_.elm.parentNode.removeChild(item_.elm);
|
|
this.items.splice(i,1);
|
|
break;
|
|
}
|
|
|
|
if(this.store)
|
|
this.save()
|
|
},
|
|
|
|
/// Save a playlist to local storage
|
|
save: function() {
|
|
var pl = [];
|
|
for(var i = 0; i < this.items.length; i++) {
|
|
var item = Object.assign({}, this.items[i])
|
|
delete item.elm;
|
|
delete item.playlist;
|
|
pl.push(item);
|
|
}
|
|
|
|
if(pl.length)
|
|
localStorage.setItem('playlist.' + this.name,
|
|
JSON.stringify(pl))
|
|
else
|
|
localStorage.removeItem('playlist.' + this.name);
|
|
},
|
|
|
|
/// Load playlist from local storage
|
|
load: function() {
|
|
var pl = localStorage.getItem('playlist.' + this.name);
|
|
if(pl)
|
|
this.add_list(JSON.parse(pl));
|
|
},
|
|
|
|
|
|
/// called by the player when the given item is unselected
|
|
unselect: function(player, item) {
|
|
this.tab.removeAttribute('active');
|
|
if(item.elm)
|
|
item.elm.removeAttribute('selected');
|
|
|
|
var audio = this.player.audio;
|
|
if(this.store && !audio.ended) {
|
|
item.currentTime = audio.currentTime;
|
|
this.save();
|
|
}
|
|
},
|
|
|
|
/// called by the player when the given item is selected, in order to
|
|
/// prepare it.
|
|
select: function(player, item) {
|
|
this.tab.setAttribute('active', 'true');
|
|
if(item.elm)
|
|
item.elm.setAttribute('selected', 'true');
|
|
|
|
if(this.store && item.currentTime &&
|
|
confirm("{% trans "restart from last position?" %}"))
|
|
{
|
|
// FIXME: one track in multiple playlists
|
|
player.audio.currentTime = Math.max(item.currentTime - 5, 0);
|
|
}
|
|
|
|
item.currentTime = undefined;
|
|
this.save();
|
|
},
|
|
}
|
|
|
|
|
|
player = {
|
|
/// main container of the player
|
|
player: undefined,
|
|
/// <audio> container
|
|
audio: undefined,
|
|
|
|
/// init player
|
|
init: function(id) {
|
|
this.player = document.getElementById(id);
|
|
this.audio = this.player.querySelector('audio');
|
|
|
|
var self = this;
|
|
this.audio.addEventListener('playing', function() {
|
|
self.player.setAttribute('state', 'playing');
|
|
}, false);
|
|
|
|
this.audio.addEventListener('pause', function() {
|
|
self.player.setAttribute('state', 'paused');
|
|
}, false);
|
|
|
|
this.audio.addEventListener('loadstart', function() {
|
|
self.player.setAttribute('state', 'stalled');
|
|
}, false);
|
|
|
|
this.audio.addEventListener('loadeddata', function() {
|
|
self.player.removeAttribute('state');
|
|
}, false);
|
|
|
|
this.__init_playlists()
|
|
},
|
|
|
|
__init_playlists: function() {
|
|
this.playlists = this.player.querySelector('.playlists');
|
|
this.live = new Playlist(this,
|
|
'live',
|
|
" {% trans "live" %}",
|
|
[ {% for sound in live_streams %}
|
|
{ title: "{{ sound.title }}",
|
|
url: "{{ sound.url }}",
|
|
stream: "{{ sound.url }}",
|
|
info: "{{ sound.info }}",
|
|
}, {% endfor %} ]
|
|
);
|
|
this.recents = new Playlist(this,
|
|
'recents', '{% trans "recents" %}',
|
|
[ {% for sound in last_sounds %}
|
|
{ title: "{{ sound.title }}",
|
|
url: "{{ sound.url }}",
|
|
{% if sound.related.embed %}
|
|
embed: "{{ sound.related.embed }}",
|
|
{% else %}
|
|
stream: "{{ MEDIA_URL }}{{ sound.related.url|safe }}",
|
|
{% endif %}
|
|
info: "{{ sound.related.duration|date:"i:s" }}",
|
|
}, {% endfor %} ]
|
|
);
|
|
this.marked = new Playlist(this,
|
|
'marked', '★ {% trans "marked" %}', null, true);
|
|
this.playlist = new Playlist(this,
|
|
'playlist', '☰ {% trans "playlist" %}', null, true);
|
|
|
|
this.select(this.live.items[0], false);
|
|
this.select_playlist(this.recents);
|
|
this.update_on_air();
|
|
},
|
|
|
|
|
|
/** player actions **/
|
|
/// play a given item { title, src }
|
|
play: function() {
|
|
var player = this.player;
|
|
var audio = this.audio;
|
|
|
|
if(audio.paused) {
|
|
audio.play();
|
|
}
|
|
else {
|
|
audio.pause();
|
|
}
|
|
},
|
|
|
|
/// select the current track to play, and start playing it
|
|
select: function(item, play = true) {
|
|
var audio = this.audio;
|
|
var player = this.player;
|
|
|
|
if(this.item && this.item.playlist)
|
|
this.item.playlist.unselect(this, this.item);
|
|
|
|
audio.pause();
|
|
audio.src = item.stream;
|
|
audio.load()
|
|
|
|
this.item = item;
|
|
if(this.item && this.item.playlist)
|
|
this.item.playlist.select(this, this.item);
|
|
|
|
player.querySelectorAll('#simple-player .title')[0]
|
|
.innerHTML = item.title;
|
|
|
|
if(play)
|
|
this.play();
|
|
},
|
|
|
|
/// remove selection using the given selector.
|
|
__unselect: function (selector) {
|
|
v = this.player.querySelectorAll(selector);
|
|
if(v)
|
|
for(var i = 0; i < v.length; i++)
|
|
v[i].removeAttribute('selected');
|
|
},
|
|
|
|
/// select current playlist to show
|
|
select_playlist: function(playlist) {
|
|
this.__unselect('.playlists nav .tab[selected]');
|
|
this.__unselect('.playlists .playlist[selected]');
|
|
|
|
self.playlist = playlist
|
|
if(playlist) {
|
|
playlist.playlist.setAttribute('selected', 'true');
|
|
playlist.tab.setAttribute('selected', 'true');
|
|
}
|
|
},
|
|
|
|
/** utility **/
|
|
/// update on air informations
|
|
update_on_air: function() {
|
|
part = Part('{% url "exp.player.on_air" %}').get()
|
|
.select({
|
|
title: '.title',
|
|
url: ['.url', 'href'],
|
|
})
|
|
.map(this.player.querySelector('.on_air'))
|
|
.send();
|
|
|
|
window.setTimeout(function() {
|
|
player.update_on_air();
|
|
}, 60000*5);
|
|
},
|
|
|
|
/// add sound actions to a given element
|
|
add_actions: function(item, container) {
|
|
var player = this;
|
|
|
|
var actions = player.player.querySelector('.actions')
|
|
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);
|
|
|
|
// TODO: remove "play" action
|
|
if(!elm.parentNode)
|
|
container.appendChild(elm);
|
|
},
|
|
}
|
|
|
|
player.init('player')
|
|
</script>
|
|
{% endblock %}
|
|
|