forked from rc/aircox
work on admin interface, player, list of sounds
This commit is contained in:
@ -24,6 +24,9 @@ ul {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
max-width: 2em;
|
||||
@ -67,9 +70,11 @@ nav.menu {
|
||||
padding: 0.2em;
|
||||
height: 2.5em;
|
||||
margin-bottom: 1em;
|
||||
background-color: white;
|
||||
box-shadow: 0em 0em 0.2em black;
|
||||
}
|
||||
.menu.top * {
|
||||
vertical-align: middle;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.menu.top > section {
|
||||
@ -81,6 +86,10 @@ nav.menu {
|
||||
margin: 0.2em 1em;
|
||||
}
|
||||
|
||||
.page_left, .page_right {
|
||||
max-width: 16em;
|
||||
}
|
||||
|
||||
.page_left > section,
|
||||
.page_right > section {
|
||||
margin-bottom: 1em;
|
||||
@ -121,6 +130,12 @@ ul.list {
|
||||
}
|
||||
|
||||
|
||||
.list nav {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
/** content: date list **/
|
||||
.date_list nav {
|
||||
text-align:center;
|
||||
@ -188,8 +203,6 @@ ul.list {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.comments ul {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
@ -208,19 +221,23 @@ ul.list {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/** content: player **/
|
||||
.player {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.player:not([seekable]) > .controls {
|
||||
.player:not([seekable]) > .controls > .progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.player .controls > * {
|
||||
margin: 0em 0.2em;
|
||||
.player .controls {
|
||||
margin-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
.player .controls > * {
|
||||
margin: 0em 0.2em;
|
||||
}
|
||||
|
||||
.player .controls .single {
|
||||
display: none;
|
||||
@ -245,14 +262,9 @@ ul.list {
|
||||
border-right: 2px #818181 solid;
|
||||
}
|
||||
|
||||
.player .on_air a:not([href]), .on_air a[href=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.player .playlist .item {
|
||||
margin: 0em;
|
||||
padding: 0.2em 0.4em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -266,25 +278,20 @@ ul.list {
|
||||
|
||||
.player .playlist .item .actions {
|
||||
display: none;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.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] {
|
||||
height: auto;
|
||||
font-size: 1.1em;
|
||||
border-left: 1px #007EDF solid;
|
||||
font-size: 1.0em;
|
||||
}
|
||||
|
||||
.player .item:not([selected]) {
|
||||
}
|
||||
|
||||
.player .button {
|
||||
@ -300,36 +307,33 @@ ul.list {
|
||||
max-height: 2.0em;
|
||||
}
|
||||
|
||||
.player:not([state]) .button > img:not(.play),
|
||||
.player[state="paused"] .button > img:not(.play),
|
||||
.player[state="playing"] .button > img:not(.pause),
|
||||
.player[state="loading"] .button > img:not(.loading)
|
||||
|
||||
.player:not([state]) .item[selected] .button > img:not(.play),
|
||||
.player[state="paused"] .item[selected] .button > img:not(.play),
|
||||
.player[state="playing"] .item[selected] .button > img:not(.pause),
|
||||
.player[state="loading"] .item[selected] .button > img:not(.loading)
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.player[state="loading"] .box .button > img.loading {
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: rotate;
|
||||
animation-timing-function: linear;
|
||||
.player .item:not([selected]) .button > img.play {
|
||||
display: block;
|
||||
}
|
||||
.player .item:not([selected]) .button > img:not(.play) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.player .list_item.live:hover .actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/** content: page **/
|
||||
main .body ~ section:not(.comments) {
|
||||
width: calc(50% - 1em);
|
||||
float: left;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
@ -50,6 +50,7 @@ time {
|
||||
|
||||
.info {
|
||||
font-size: 0.9em;
|
||||
padding: 0.1em;
|
||||
color: #007EDF;
|
||||
}
|
||||
|
||||
@ -81,14 +82,69 @@ main {
|
||||
box-shadow: 0em 0em 0.2em black;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
margin: 0em;
|
||||
main h1:not(.detail_title) {
|
||||
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);
|
||||
margin-top: -3.3em;
|
||||
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.detail = detail;
|
||||
this.duration = duration;
|
||||
this.streams = streams.splice ? streams.sort() : [streams];
|
||||
this.cover = cover;
|
||||
this.on_air = on_air;
|
||||
}
|
||||
|
||||
Sound.prototype = {
|
||||
@ -30,25 +32,60 @@ Sound.prototype = {
|
||||
detail: '',
|
||||
streams: undefined,
|
||||
duration: undefined,
|
||||
on_air_url: undefined,
|
||||
cover: undefined,
|
||||
on_air: false,
|
||||
|
||||
item: undefined,
|
||||
|
||||
get seekable() {
|
||||
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.playlist = player.player.querySelector('.playlist');
|
||||
this.item_ = player.player.querySelector('.playlist .item');
|
||||
this.sounds = []
|
||||
}
|
||||
|
||||
PlayerPlaylist.prototype = {
|
||||
Playlist.prototype = {
|
||||
on_air: undefined,
|
||||
sounds: undefined,
|
||||
sound: undefined,
|
||||
|
||||
/// Find a sound by its streams, and return it if found
|
||||
find: function(streams) {
|
||||
@ -71,30 +108,12 @@ PlayerPlaylist.prototype = {
|
||||
if(sound_)
|
||||
return sound_;
|
||||
|
||||
var item = this.item_.cloneNode(true);
|
||||
item.removeAttribute('style');
|
||||
if(sound.on_air)
|
||||
this.on_air = sound;
|
||||
|
||||
item.querySelector('.title').innerHTML = sound.title;
|
||||
if(sound.seekable)
|
||||
item.querySelector('.duration').innerHTML =
|
||||
duration_str(sound.duration);
|
||||
if(sound.detail)
|
||||
item.querySelector('.detail').href = sound.detail;
|
||||
sound.make_item(this, this.item_);
|
||||
(container || this.playlist).appendChild(sound.item);
|
||||
|
||||
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.save();
|
||||
return sound;
|
||||
@ -106,8 +125,61 @@ PlayerPlaylist.prototype = {
|
||||
this.sounds.splice(index,1);
|
||||
this.playlist.removeChild(sound.item);
|
||||
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() {
|
||||
var list = [];
|
||||
for(var i in this.sounds) {
|
||||
@ -124,7 +196,7 @@ PlayerPlaylist.prototype = {
|
||||
for(var i in list) {
|
||||
var sound = list[i];
|
||||
sound = new Sound(sound.title, sound.detail, sound.duration,
|
||||
sound.streams)
|
||||
sound.streams, sound.cover, sound.on_air)
|
||||
this.add(sound, container)
|
||||
}
|
||||
this.playlist.appendChild(container);
|
||||
@ -132,53 +204,67 @@ PlayerPlaylist.prototype = {
|
||||
}
|
||||
|
||||
|
||||
function Player(id) {
|
||||
this.store = new Store('player');
|
||||
function Player(id, on_air_url, show_cover) {
|
||||
this.id = id;
|
||||
this.on_air_url = on_air_url;
|
||||
this.show_cover = show_cover;
|
||||
|
||||
this.store = new Store('player_' + id);
|
||||
|
||||
// html sounds
|
||||
this.player = document.getElementById(id);
|
||||
this.audio = this.player.querySelector('audio');
|
||||
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 = {
|
||||
duration: this.player.querySelector('.controls .duration'),
|
||||
progress: this.player.querySelector('progress'),
|
||||
single: this.player.querySelector('input.single'),
|
||||
}
|
||||
|
||||
this.playlist = new PlayerPlaylist(this);
|
||||
this.playlist = new Playlist(this);
|
||||
this.playlist.load();
|
||||
|
||||
this.init_events();
|
||||
this.load();
|
||||
|
||||
this.update_on_air();
|
||||
}
|
||||
|
||||
Player.prototype = {
|
||||
/// current item being played
|
||||
sound: undefined,
|
||||
on_air_url: undefined,
|
||||
|
||||
init_events: function() {
|
||||
var self = this;
|
||||
|
||||
function time_from_progress(event) {
|
||||
bounding = self.controls.progress.getBoundingClientRect()
|
||||
bounding = self.progress.bar.getBoundingClientRect()
|
||||
offset = (event.clientX - bounding.left);
|
||||
return offset * self.audio.duration / bounding.width;
|
||||
}
|
||||
|
||||
function update_info() {
|
||||
var controls = self.controls;
|
||||
var progress = self.progress;
|
||||
var pos = self.audio.currentTime;
|
||||
|
||||
// progress
|
||||
if(!self.sound || !self.sound.seekable ||
|
||||
self.audio.duration == Infinity) {
|
||||
controls.duration.innerHTML = '';
|
||||
controls.progress.value = 0;
|
||||
!pos || self.audio.duration == Infinity)
|
||||
{
|
||||
progress.duration.innerHTML = '';
|
||||
progress.bar.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = self.audio.currentTime;
|
||||
controls.progress.value = pos;
|
||||
controls.progress.max = self.audio.duration;
|
||||
controls.duration.innerHTML = duration_str(pos);
|
||||
progress.bar.value = pos;
|
||||
progress.bar.max = self.audio.duration;
|
||||
progress.duration.innerHTML = duration_str(pos);
|
||||
}
|
||||
|
||||
// audio
|
||||
@ -207,7 +293,7 @@ Player.prototype = {
|
||||
}, false);
|
||||
|
||||
// progress
|
||||
progress = this.controls.progress;
|
||||
progress = this.progress.bar;
|
||||
progress.addEventListener('click', function(event) {
|
||||
player.audio.currentTime = time_from_progress(event);
|
||||
}, false);
|
||||
@ -215,43 +301,55 @@ Player.prototype = {
|
||||
progress.addEventListener('mouseout', update_info, false);
|
||||
|
||||
progress.addEventListener('mousemove', function(event) {
|
||||
if(self.audio.duration == Infinity)
|
||||
if(self.audio.duration == Infinity || isNaN(self.audio.duration))
|
||||
return;
|
||||
|
||||
var pos = time_from_progress(event);
|
||||
self.controls.duration.innerHTML = duration_str(pos);
|
||||
self.progress.duration.innerHTML = duration_str(pos);
|
||||
}, false);
|
||||
},
|
||||
|
||||
update_on_air: function(url) {
|
||||
if(!url) {
|
||||
// TODO HERE
|
||||
}
|
||||
update_on_air: function() {
|
||||
if(!this.on_air_url)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
window.setTimeout(function() {
|
||||
self.update_on_air();
|
||||
}, 60*1000);
|
||||
|
||||
if(!this.playlist.on_air)
|
||||
return;
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
req.open('GET', this.on_air_url, true);
|
||||
req.onreadystatechange = function() {
|
||||
if(req.readyState != 4 || (req.status != 200 && req.status != 0))
|
||||
if(req.readyState != 4 || (req.status != 200 &&
|
||||
req.status != 0))
|
||||
return;
|
||||
|
||||
var data = JSON.parse(req.responseText)
|
||||
if(data.type == 'track') {
|
||||
self.on_air.querySelector('.info').innerHTML = '♫';
|
||||
self.on_air.querySelector('.title') =
|
||||
(data.artist || '') + ' — ' + (data.title);
|
||||
self.on_air.querySelector('.url').removeAttribute('href');
|
||||
}
|
||||
else {
|
||||
self.on_air.querySelector('.info').innerHTML = '';
|
||||
self.on_air.querySelector('.title').innerHTML = data.title;
|
||||
self.on_air.querySelector('.url').setAttribute('href', data.url);
|
||||
}
|
||||
if(data.type == 'track')
|
||||
data = {
|
||||
title: '♫' + (data.artist ? data.artist + ' — ' : '') +
|
||||
data.title,
|
||||
url: ''
|
||||
}
|
||||
else
|
||||
data = {
|
||||
title: data.title,
|
||||
info: '',
|
||||
url: data.url
|
||||
}
|
||||
|
||||
if(timeout)
|
||||
window.setTimeout(function() {
|
||||
self.update_on_air(url);
|
||||
}, 60*1000);
|
||||
var on_air = self.playlist.on_air;
|
||||
on_air = on_air.item.querySelector('.content');
|
||||
|
||||
if(data.url)
|
||||
on_air.innerHTML =
|
||||
'<a href="' + data.url + '">' + data.title + '</a>';
|
||||
else
|
||||
on_air.innerHTML = data.title;
|
||||
};
|
||||
req.send();
|
||||
},
|
||||
@ -263,78 +361,44 @@ Player.prototype = {
|
||||
this.audio.pause();
|
||||
},
|
||||
|
||||
unselect: function(sound) {
|
||||
sound.item.removeAttribute('selected');
|
||||
},
|
||||
|
||||
__mime_type: function(path) {
|
||||
ext = path.substr(path.lastIndexOf('.')+1);
|
||||
return 'audio/' + ext;
|
||||
},
|
||||
|
||||
select: function(sound, play = true) {
|
||||
if(this.sound == sound) {
|
||||
if(play)
|
||||
this.play();
|
||||
return;
|
||||
}
|
||||
load_sound: function(sound) {
|
||||
var audio = this.audio;
|
||||
audio.pause();
|
||||
|
||||
if(this.sound)
|
||||
this.unselect(this.sound);
|
||||
|
||||
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]);
|
||||
}
|
||||
var sources = audio.querySelectorAll('source');
|
||||
for(var i = 0; i < sources.length; i++)
|
||||
audio.removeChild(sources[i]);
|
||||
|
||||
streams = sound.streams;
|
||||
for(var i = 0; i < streams.length; i++) {
|
||||
var source = document.createElement('source');
|
||||
source.src = streams[i];
|
||||
source.type = this.__mime_type(source.src);
|
||||
this.audio.appendChild(source);
|
||||
audio.appendChild(source);
|
||||
}
|
||||
this.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);
|
||||
audio.load();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
// TODO: move stored sound into playlist
|
||||
this.store.set('player', {
|
||||
single: this.controls.single.checked,
|
||||
sound: this.sound && this.sound.streams,
|
||||
sound: this.playlist.sound && this.playlist.sound.streams,
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
var data = this.store.get('player');
|
||||
if(!data)
|
||||
return;
|
||||
this.controls.single.checked = data.single;
|
||||
if(data.sound)
|
||||
this.sound = this.playlist.find(data.sound);
|
||||
this.playlist.sound = this.playlist.find(data.sound);
|
||||
},
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user