one sound, one parent: Diffusion.sounds -> Sound.diffusion; player: playerStore, save pos when time update, single mode
This commit is contained in:
parent
ceecbcfa54
commit
2564ba298a
10
notes.md
10
notes.md
|
@ -11,8 +11,11 @@
|
||||||
- (old) schedule.to_string unused? commented
|
- (old) schedule.to_string unused? commented
|
||||||
- write more tests
|
- write more tests
|
||||||
- sounds:
|
- sounds:
|
||||||
- inline admin
|
- check that a sound is available when uploading too
|
||||||
- one sound, one diffusion?
|
- one sound, one diffusion
|
||||||
|
-> update admin & inlining
|
||||||
|
-> sound_monitor
|
||||||
|
-> liquidsoap
|
||||||
|
|
||||||
- liquidsoap:
|
- liquidsoap:
|
||||||
- update rc's supervisor scripts
|
- update rc's supervisor scripts
|
||||||
|
@ -34,6 +37,7 @@
|
||||||
- article list with the focus
|
- article list with the focus
|
||||||
- similar articles (using tags)
|
- similar articles (using tags)
|
||||||
- calendar
|
- calendar
|
||||||
|
- tags: allow tags_url on all publications
|
||||||
|
|
||||||
- website:
|
- website:
|
||||||
- diffusions:
|
- diffusions:
|
||||||
|
@ -42,10 +46,8 @@
|
||||||
- print program's name in lists
|
- print program's name in lists
|
||||||
- player:
|
- player:
|
||||||
- "listen" + "favorite" buttons made easy + automated
|
- "listen" + "favorite" buttons made easy + automated
|
||||||
- single mode / play next auto
|
|
||||||
- mixcloud
|
- mixcloud
|
||||||
- seek bar
|
- seek bar
|
||||||
- section for schedule as calendar
|
|
||||||
- load complete week for a schedule?
|
- load complete week for a schedule?
|
||||||
- finish that fucking website
|
- finish that fucking website
|
||||||
- list of played diffusions and tracks when non-stop;
|
- list of played diffusions and tracks when non-stop;
|
||||||
|
|
|
@ -25,7 +25,7 @@ class StreamInline(admin.TabularInline):
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
class SoundDiffInline(admin.TabularInline):
|
class SoundDiffInline(admin.TabularInline):
|
||||||
model = Diffusion.sounds.through
|
model = Sound
|
||||||
|
|
||||||
# from suit.admin import SortableTabularInline, SortableModelAdmin
|
# from suit.admin import SortableTabularInline, SortableModelAdmin
|
||||||
#class TrackInline(SortableTabularInline):
|
#class TrackInline(SortableTabularInline):
|
||||||
|
|
|
@ -104,15 +104,17 @@ class Sound(Nameable):
|
||||||
"""
|
"""
|
||||||
A Sound is the representation of a sound file that can be either an excerpt
|
A Sound is the representation of a sound file that can be either an excerpt
|
||||||
or a complete archive of the related diffusion.
|
or a complete archive of the related diffusion.
|
||||||
|
|
||||||
The podcasting and public access permissions of a Sound are managed through
|
|
||||||
the related program info.
|
|
||||||
"""
|
"""
|
||||||
class Type(IntEnum):
|
class Type(IntEnum):
|
||||||
other = 0x00,
|
other = 0x00,
|
||||||
archive = 0x01,
|
archive = 0x01,
|
||||||
excerpt = 0x02,
|
excerpt = 0x02,
|
||||||
|
|
||||||
|
diffusion = models.ForeignKey(
|
||||||
|
'Diffusion',
|
||||||
|
verbose_name = _('diffusion'),
|
||||||
|
blank = True, null = True,
|
||||||
|
)
|
||||||
type = models.SmallIntegerField(
|
type = models.SmallIntegerField(
|
||||||
verbose_name = _('type'),
|
verbose_name = _('type'),
|
||||||
choices = [ (int(y), _(x)) for x,y in Type.__members__.items() ],
|
choices = [ (int(y), _(x)) for x,y in Type.__members__.items() ],
|
||||||
|
@ -550,11 +552,6 @@ class Diffusion(models.Model):
|
||||||
'Program',
|
'Program',
|
||||||
verbose_name = _('program'),
|
verbose_name = _('program'),
|
||||||
)
|
)
|
||||||
sounds = models.ManyToManyField(
|
|
||||||
Sound,
|
|
||||||
blank = True,
|
|
||||||
verbose_name = _('sounds'),
|
|
||||||
)
|
|
||||||
# specific
|
# specific
|
||||||
type = models.SmallIntegerField(
|
type = models.SmallIntegerField(
|
||||||
verbose_name = _('type'),
|
verbose_name = _('type'),
|
||||||
|
|
|
@ -64,10 +64,15 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlists nav a.close {
|
.playlists nav > a.close,
|
||||||
|
.playlists nav > label {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#player-single-mode + label[for="player-single-mode"]::after {
|
||||||
|
content:"{% trans "single mode" %}";
|
||||||
|
}
|
||||||
|
|
||||||
.playlist {
|
.playlist {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -136,9 +141,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="playlists">
|
<div class="playlists">
|
||||||
<nav>
|
<nav>
|
||||||
<a onclick="player.select_playlist()" class="close"
|
<input type="checkbox" class="single" id="player-single-mode">
|
||||||
title="{% trans "close" %}">✖</a>
|
<label for="player-single-mode"></label>
|
||||||
<!-- single mode -->
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class='item on_air'>
|
<div class='item on_air'>
|
||||||
|
@ -148,6 +152,46 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
playerStore = {
|
||||||
|
// save data to localstorage, or remove it if data is null
|
||||||
|
set: function(name, data) {
|
||||||
|
name = 'player.' + name;
|
||||||
|
if(data == undefined) {
|
||||||
|
localStorage.removeItem(name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localStorage.setItem(name, JSON.stringify(data))
|
||||||
|
},
|
||||||
|
|
||||||
|
// load data from localstorage
|
||||||
|
get: function(name) {
|
||||||
|
try {
|
||||||
|
name = 'player.' + name;
|
||||||
|
var data = localStorage.getItem(name);
|
||||||
|
if(data)
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
catch(e) { console.log(e, data); }
|
||||||
|
},
|
||||||
|
|
||||||
|
// return true if the given item is stored
|
||||||
|
exists: function(name) {
|
||||||
|
name = 'player.' + name;
|
||||||
|
return (localStorage.getItem(name) != null);
|
||||||
|
},
|
||||||
|
|
||||||
|
// update a field in the stored data
|
||||||
|
update: function(name, key, value) {
|
||||||
|
data = this.get(name) || {};
|
||||||
|
if(value)
|
||||||
|
data[key] = value;
|
||||||
|
else
|
||||||
|
delete data[key];
|
||||||
|
this.set(name, data);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create a Playlist:
|
// Create a Playlist:
|
||||||
// * name: name of the playlist, used for container id and storage
|
// * name: name of the playlist, used for container id and storage
|
||||||
// * tab: text to put in the tab
|
// * tab: text to put in the tab
|
||||||
|
@ -185,7 +229,12 @@ Playlist.prototype = {
|
||||||
items: undefined,
|
items: undefined,
|
||||||
|
|
||||||
/// find an item in playlist
|
/// find an item in playlist
|
||||||
find: function(item) {
|
find: function(item, by_stream = false) {
|
||||||
|
if(by_stream)
|
||||||
|
return this.items.find(function(v) {
|
||||||
|
return v.stream == item;
|
||||||
|
});
|
||||||
|
|
||||||
return this.items.find(function(v) {
|
return this.items.find(function(v) {
|
||||||
return v.stream == item.stream;
|
return v.stream == item.stream;
|
||||||
});
|
});
|
||||||
|
@ -276,22 +325,16 @@ Playlist.prototype = {
|
||||||
delete item.playlist;
|
delete item.playlist;
|
||||||
pl.push(item);
|
pl.push(item);
|
||||||
}
|
}
|
||||||
|
playerStore.set('playlist.' + this.name, pl)
|
||||||
if(pl.length)
|
|
||||||
localStorage.setItem('playlist.' + this.name,
|
|
||||||
JSON.stringify(pl))
|
|
||||||
else
|
|
||||||
localStorage.removeItem('playlist.' + this.name);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Load playlist from local storage
|
/// Load playlist from local storage
|
||||||
load: function() {
|
load: function() {
|
||||||
var pl = localStorage.getItem('playlist.' + this.name);
|
var pl = playerStore.get('playlist.' + this.name);
|
||||||
if(pl)
|
if(pl)
|
||||||
this.add_list(JSON.parse(pl));
|
this.add_list(pl);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/// called by the player when the given item is unselected
|
/// called by the player when the given item is unselected
|
||||||
unselect: function(player, item) {
|
unselect: function(player, item) {
|
||||||
this.tab.removeAttribute('active');
|
this.tab.removeAttribute('active');
|
||||||
|
@ -311,16 +354,6 @@ Playlist.prototype = {
|
||||||
this.tab.setAttribute('active', 'true');
|
this.tab.setAttribute('active', 'true');
|
||||||
if(item.elm)
|
if(item.elm)
|
||||||
item.elm.setAttribute('selected', 'true');
|
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();
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,12 +363,25 @@ player = {
|
||||||
player: undefined,
|
player: undefined,
|
||||||
/// <audio> container
|
/// <audio> container
|
||||||
audio: undefined,
|
audio: undefined,
|
||||||
|
/// controls
|
||||||
|
controls: undefined,
|
||||||
|
|
||||||
/// init player
|
/// init player
|
||||||
init: function(id) {
|
init: function(id) {
|
||||||
this.player = document.getElementById(id);
|
this.player = document.getElementById(id);
|
||||||
this.audio = this.player.querySelector('audio');
|
this.audio = this.player.querySelector('audio');
|
||||||
|
this.controls = {
|
||||||
|
single: this.player.querySelector('input.single'),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: event on controls -> save info in storage
|
||||||
|
|
||||||
|
this.__init_audio();
|
||||||
|
this.__init_playlists();
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
|
||||||
|
__init_audio: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.audio.addEventListener('playing', function() {
|
this.audio.addEventListener('playing', function() {
|
||||||
self.player.setAttribute('state', 'playing');
|
self.player.setAttribute('state', 'playing');
|
||||||
|
@ -353,7 +399,19 @@ player = {
|
||||||
self.player.removeAttribute('state');
|
self.player.removeAttribute('state');
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
this.__init_playlists()
|
this.audio.addEventListener('timeupdate', function() {
|
||||||
|
if(self.audio.seekable.length)
|
||||||
|
playerStore.set('stream.' + self.item.stream + '.pos',
|
||||||
|
self.audio.currentTime)
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
this.audio.addEventListener('ended', function() {
|
||||||
|
playerStore.set('streams.' + self.item.stream + '.pos')
|
||||||
|
|
||||||
|
single = self.player.querySelector('input.single');
|
||||||
|
if(!single.checked)
|
||||||
|
self.next(true);
|
||||||
|
}, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
__init_playlists: function() {
|
__init_playlists: function() {
|
||||||
|
@ -391,6 +449,27 @@ player = {
|
||||||
this.update_on_air();
|
this.update_on_air();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
var data = playerStore.get('player');
|
||||||
|
if(!data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(data.playlist)
|
||||||
|
this.select_playlist(this[data.playlist]);
|
||||||
|
if(data.stream) {
|
||||||
|
item = this.playlist.find(data.stream, true);
|
||||||
|
item && this.select(item, false);
|
||||||
|
}
|
||||||
|
this.controls.single.checked = data.single
|
||||||
|
},
|
||||||
|
|
||||||
|
save: function() {
|
||||||
|
playerStore.set('player', {
|
||||||
|
'playlist': this.playlist.name,
|
||||||
|
'stream': this.item && this.item.stream,
|
||||||
|
'single': this.controls.single.checked,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/** player actions **/
|
/** player actions **/
|
||||||
/// play a given item { title, src }
|
/// play a given item { title, src }
|
||||||
|
@ -406,6 +485,16 @@ player = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
__ask_to_seek(item) {
|
||||||
|
var key = 'stream.' + item.stream + '.pos'
|
||||||
|
var pos = playerStore.get(key);
|
||||||
|
if(!pos)
|
||||||
|
return
|
||||||
|
if(confirm("{% trans "restart from the last position?" %}"))
|
||||||
|
this.audio.currentTime = Math.max(pos - 5, 0);
|
||||||
|
playerStore.set(key);
|
||||||
|
},
|
||||||
|
|
||||||
/// select the current track to play, and start playing it
|
/// select the current track to play, and start playing it
|
||||||
select: function(item, play = true) {
|
select: function(item, play = true) {
|
||||||
var audio = this.audio;
|
var audio = this.audio;
|
||||||
|
@ -416,7 +505,7 @@ player = {
|
||||||
|
|
||||||
audio.pause();
|
audio.pause();
|
||||||
audio.src = item.stream;
|
audio.src = item.stream;
|
||||||
audio.load()
|
audio.load();
|
||||||
|
|
||||||
this.item = item;
|
this.item = item;
|
||||||
if(this.item && this.item.playlist)
|
if(this.item && this.item.playlist)
|
||||||
|
@ -425,8 +514,24 @@ player = {
|
||||||
player.querySelectorAll('#simple-player .title')[0]
|
player.querySelectorAll('#simple-player .title')[0]
|
||||||
.innerHTML = item.title;
|
.innerHTML = item.title;
|
||||||
|
|
||||||
if(play)
|
if(play) {
|
||||||
|
this.__ask_to_seek(item);
|
||||||
this.play();
|
this.play();
|
||||||
|
}
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Select the next track in the current playlist, eventually play it
|
||||||
|
next: function(play = true) {
|
||||||
|
var playlist = this.playlist;
|
||||||
|
var index = this.playlist.items.indexOf(this.item);
|
||||||
|
console.log(index, this.item, this.playlist.items)
|
||||||
|
if(index == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
index--;
|
||||||
|
if(index >= 0)
|
||||||
|
this.select(this.playlist.items[index], play);
|
||||||
},
|
},
|
||||||
|
|
||||||
/// remove selection using the given selector.
|
/// remove selection using the given selector.
|
||||||
|
@ -442,14 +547,14 @@ player = {
|
||||||
this.__unselect('.playlists nav .tab[selected]');
|
this.__unselect('.playlists nav .tab[selected]');
|
||||||
this.__unselect('.playlists .playlist[selected]');
|
this.__unselect('.playlists .playlist[selected]');
|
||||||
|
|
||||||
self.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');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** utility **/
|
/** utility & actions **/
|
||||||
/// update on air informations
|
/// update on air informations
|
||||||
update_on_air: function() {
|
update_on_air: function() {
|
||||||
part = Part('{% url "exp.player.on_air" %}').get()
|
part = Part('{% url "exp.player.on_air" %}').get()
|
||||||
|
@ -506,7 +611,6 @@ player = {
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// TODO: remove "play" action
|
|
||||||
if(!elm.parentNode)
|
if(!elm.parentNode)
|
||||||
container.appendChild(elm);
|
container.appendChild(elm);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user