3 $.widget( "panik.playlist", {
10 emission: "episode.slug",
11 episode: "episode.slug",
17 html5audioOptions:{controls:true,preload:"none"},
20 controlContainer: $('<div>'),
21 playlistContainer: $('<ol>'),
25 onUpdate: function(){},
26 onEnded: function(){},
29 // Initialization logic here
31 this.isActive = false;
32 this.isLastAdd = false;
33 this.controlButtons = []
34 this.debugContainer = $('<pre>').hide();
35 this.controlContainer = this.options.controlContainer;
36 this.playlistContainer = this.options.playlistContainer;
37 this.element.addClass(this.options.classes);
38 this.element.append(this.controlContainer);
39 this.element.append(this.playlistContainer);
40 this.element.append(this.debugContainer);
41 this.playlist = this.options.playlist;
42 this.buildPlaylistControls();
45 this.options.onLoad(this);
48 _setOption: function( key, value ) {
49 this.options[ key ] = value;
54 this.playlistContainer.find('audio').each(function(){
55 thePlaylist.playlist.push(thePlaylist.jsonifyAudio($(this)));
57 this.debugContainer.text(
58 JSON.stringify(this.playlist, null, '\t')
61 this.options.onUpdate(this);
67 this.playlistContainer.find('*').remove();
70 savePlaylist: function(){
71 var JSONPlaylist = JSON.stringify(this.playlist, null, '\t');
72 if (localStorage !== null && localStorage !== undefined) {
73 localStorage['playlist'] = JSON.stringify(this.playlist, null, '\t');
76 loadPlaylist: function(){
77 if (localStorage !== null && localStorage !== undefined) {
78 this.playlist = localStorage['playlist']?JSON.parse(localStorage['playlist']):this.playlist;
80 $.each(this.playlist,function(k,v){
81 thePlaylist.playlistContainer.append(thePlaylist._htmlifyJsonSound(v));
85 // Transform HTML5 <audio> to simple JSON object.
86 jsonifyAudio: function(audio) {
89 title: audio.attr('title'),
91 sound_id:audio.data('sound-id'),
92 url:audio.attr('data-url'),
93 focus:audio.attr('data-player-focus')
95 audio.children('source').each(function(){
96 sound.source[$(this).attr('type')] = $(this).attr('src');
101 setFocus: function(element) {
102 this.isActive = element;
103 this.playlistContainer.find('li').each(function(){
104 $(this).removeClass('active');
105 $(this).find('audio').removeAttr('data-player-focus');
107 this.isActive.addClass('active').find('audio').attr('data-player-focus',true);
110 // Transform JSON sound object to HTML container for playlist.
111 _htmlifyJsonSound: function(sound) {
112 var container = $('<li>').attr('data-origin',sound.id);
113 var audio = $('<audio>',this.options.html5audioOptions)
114 .attr('title',sound.title).hide()
115 .attr('data-sound-id', sound.sound_id)
116 .attr('data-url',sound.url);
117 var audio_str = sound.sound_id + ': ' + sound.title;
118 $.each(sound.source,function(k,v){
119 var source = $('<source>',{src:v,type:k});
120 audio.append(source);
122 audio.on('play',function(e){
123 if (e.originalEvent === undefined) {
126 if (typeof (_paq) == 'object') {
127 _paq.push(['trackEvent', 'Audio', 'Play', audio_str]);
129 thePlaylist.setFocus(container);
130 container.addClass('playing');
131 playpause.addClass('action-pause').removeClass('action-play');
132 thePlaylist.controlButtons['playpause'].removeClass('action-play').addClass('action-pause');
133 thePlaylist.afterPlay();
134 var sound_id = $(e.target).data('sound-id');
135 $(document).trigger('panik:play', {'sound_id': sound_id});
136 }).on('pause',function(e){
137 $(this).removeClass('playing');
138 playpause.addClass('action-play').removeClass('action-pause');
139 var sound_id = $(e.target).data('sound-id');
140 $(document).trigger('panik:pause', {'sound_id': sound_id});
141 thePlaylist.controlButtons['playpause'].removeClass('action-pause').addClass('action-play');
142 }).on('stop',function(event){
143 $(this).trigger('pause');
144 if($(this)[0].currentTime){$(this)[0].currentTime = 0;}
145 }).on("ended", function(e){
146 if (typeof (_paq) == 'object') {
147 _paq.push(['trackEvent', 'Audio', 'End', audio_str]);
150 }).on('beforePause',function(){
152 }).on('beforePlay',function(){
153 thePlaylist.pauseSounds();
155 }).on('timeupdate', function(event) {
156 var position = 1.0 * event.target.currentTime / event.target.duration;
157 var sound_id = $(event.target).data('sound-id');
158 $(document).trigger('panik:timeupdate',
159 {'sound_id': sound_id, 'position': position});
161 var controls = $('<span>',{'class':'soundControls controls'});
162 var link = $('<a>',{href:sound.url,'class':'button icon-external-link'});
163 var remove = $('<button>',{title:"Stop",'class':'no-icon-remove',click:function(){
165 thePlaylist._update();
167 var stop = $('<button>',{title:"Stop",'class':'icon-stop',click:function(){
168 audio.trigger('stop');
170 var playpause = $('<button>',{title:"Play/Pause",'class':'action-play',click:function(){
172 if (audio[0].paused == false) { audio.trigger('pause');
174 audio.trigger('beforePlay').trigger('play');
177 controls.append(playpause).append(stop).append(remove);
178 var title = $('<a>',{title:"More information",href:sound.url,'class':"button title",html:sound.title});
179 container.append(controls).append(title).append(audio);
180 if(sound.focus){thePlaylist.setFocus(container);}
183 // Create a public method.
184 registerAudio: function(audio,success) {
185 var sound_id = audio.data('sound-id');
186 if (this.playlistContainer.find('[data-sound-id=' + sound_id + ']').length) {
187 /* already in playlist */
190 var audioObj = this.jsonifyAudio(audio);
191 var htmlAudio = this._htmlifyJsonSound(audioObj);
192 this.playlistContainer.append(htmlAudio);
193 this.isLastAdd = htmlAudio;
194 if(!this.isActive){this.setFocus(this.isLastAdd);}
195 this.options.onAdd(this);
196 if(success){success();}
200 bindControl: function(control,audio,element,options) {
201 element.addClass('loading');
202 audioID = audio.attr('id');
203 //TODO for controls in page content
206 registerControl: function(name,options) {
207 this.controlButtons[name] = $('<button>',options);
208 this.controlContainer.append(this.controlButtons[name]);
211 buildPlaylistControls: function() {
212 this.controlContainer.empty();
214 this.registerControl('toggleList',{class:"icon-list",click: function(){
215 thePlaylist.playlistContainer.toggle();
217 this.registerControl('clearList',{class:"icon-trash",click: function(){
218 thePlaylist.playlistContainer.empty();
219 thePlaylist._update();
222 this.registerControl('playpause',{'class':"action-play playPause",click: function(){
223 thePlaylist.playPauseList();
228 afterPlay: function() {
229 console.log('after play');
230 this.options.onPlay(this);
233 beforePlay: function() {
234 console.log('before play');
238 getActive: function() {
240 this.isActive = this.playlistContainer.children('li').first();
242 return this.isActive;
244 playSoundId: function(sound_id) {
245 this.playlistContainer.find('audio[data-sound-id="' + sound_id + '"]'
246 ).trigger('beforePlay').trigger('play');
249 playPauseList: function() {
250 if(this.controlButtons['playpause'].hasClass('action-play')){
251 this.getActive().find('audio').trigger('beforePlay').trigger('play');
253 this.getActive().find('audio').trigger('pause');
258 playPrevious: function() {
259 this.getActive().prev().find('audio').trigger('beforePlay').trigger('play');
263 playNext: function() {
264 this.getActive().next().find('audio').trigger('beforePlay').trigger('play');
268 playFirst: function() {
269 this.playlistContainer.find('audio').first().trigger('beforePlay').trigger('play');
273 playLast: function() {
274 this.playlistContainer.find('audio').last().trigger('beforePlay').trigger('play');
278 pauseSounds: function() {
279 this.playlistContainer.find('audio').each(function(){$(this).trigger('pause');});
282 this.options.onEnded(this);
285 stopSounds: function() {
286 this.playlistContainer.find('audio').each(function(){$(this).trigger('stop');});
289 this.isActive = false;
291 this.playlistContainer.find('*').remove();