]> source.dussan.org Git - nextcloud-server.git/commitdiff
Some much needed interface work on the media player
authorRobin Appelman <icewind1991@gmail.com>
Fri, 29 Jul 2011 21:53:22 +0000 (23:53 +0200)
committerRobin Appelman <icewind1991@gmail.com>
Fri, 29 Jul 2011 21:55:54 +0000 (23:55 +0200)
13 files changed:
apps/media/ajax/api.php
apps/media/appinfo/app.php
apps/media/css/music.css
apps/media/css/player.css
apps/media/index.php
apps/media/js/collection.js [new file with mode: 0644]
apps/media/js/music.js
apps/media/js/player.js
apps/media/js/playlist.js [new file with mode: 0644]
apps/media/lib_collection.php
apps/media/templates/collection.php [new file with mode: 0644]
apps/media/templates/music.php
apps/media/templates/playlist.php [new file with mode: 0644]

index 225bce7b092ebca36341d9d7f4a32faec50572cf..b86c69d0beb8c8252f7cba46abe5e8c306515e93 100644 (file)
@@ -67,7 +67,6 @@ if($arguments['action']){
                        $artists=OC_MEDIA_COLLECTION::getArtists();
                        foreach($artists as &$artist){
                                $artist['albums']=OC_MEDIA_COLLECTION::getAlbums($artist['artist_id']);
-                               $artistHasSongs=false;
                                foreach($artist['albums'] as &$album){
                                        $album['songs']=OC_MEDIA_COLLECTION::getSongs($artist['artist_id'],$album['album_id']);
                                }
index d75bac031e9040ad60d88907aadf1974edc47482..fbfa441f93d2c46608a0fe7ba5be35fbef16ca48 100644 (file)
@@ -28,6 +28,36 @@ if(OC_App::getCurrentApp()=='files'){
 
 OC_App::register( array( 'order' => 3, 'id' => 'media', 'name' => 'Media' ));
 
-OC_App::addNavigationEntry( array( 'id' => 'media_index', 'order' => 2, 'href' => OC_Helper::linkTo( 'media', 'index.php' ), 'icon' => OC_Helper::imagePath( 'media', 'media.png' ), 'name' => 'Media' ));
-OC_App::addSettingsPage( array( 'id' => 'media_settings', 'order' => 5, 'href' => OC_Helper::linkTo( 'media', 'settings.php' ), 'name' => 'Media', 'icon' => OC_Helper::imagePath( 'media', 'media.png' )));
+OC_APP::addNavigationEntry( array( 'id' => 'media_index', 'order' => 2, 'href' => OC_HELPER::linkTo( 'media', 'index.php' ), 'icon' => OC_HELPER::imagePath( 'media', 'media.png' ), 'name' => 'Media' ));
+OC_APP::addSettingsPage( array( 'id' => 'media_settings', 'order' => 5, 'href' => OC_HELPER::linkTo( 'media', 'settings.php' ), 'name' => 'Media', 'icon' => OC_HELPER::imagePath( 'media', 'media.png' )));
+
+// add subnavigations
+$entry = array(
+       'id' => "media_playlist",
+       'order'=>1,
+       'href' => '#playlist',
+       'name' => 'Playlist'
+);
+OC_APP::addNavigationSubEntry( "media_index", $entry);
+$entry = array(
+       'id' => "media_collection",
+       'order'=>1,
+       'href' => '#collection',
+       'name' => 'Collection'
+);
+OC_APP::addNavigationSubEntry( "media_index", $entry);
+$entry = array(
+       'id' => "media_recent",
+       'order'=>1,
+       'href' => '#recent',
+       'name' => 'Most Recent'
+);
+OC_APP::addNavigationSubEntry( "media_index", $entry);
+$entry = array(
+       'id' => "media_mostplayer",
+       'order'=>1,
+       'href' => '#mostplayed',
+       'name' => 'Most Played'
+);
+OC_APP::addNavigationSubEntry( "media_index", $entry);
 ?>
index 92a4ea5e147d78706ca54fa7ebda8123d9e67854..067da79446d798f5d5ea99cf68c1a527e5d75af4 100644 (file)
@@ -3,7 +3,15 @@
 li button.right.prettybutton{font-size:1em;}
 #collection{padding-top:1em;position:relative;width:70ex;float:left;}
 #collection li.album,#collection li.song{margin-left:3ex;}
-#playlist{margin-left:72ex;}
-#playlist li.current{background-color:#ccc;}
+#playlist{width:100%;border-spacing:0;}
+#playlist th{background-color:#ccc; text-align:left; font-size:1.2em; padding:0.2em}
+#playlist tr.selected{background-color:#eee;}
+#playlist tr.current{background-color:#ccc;}
+#playlist td.time, #playlist th.time{text-align:right; padding-right:1em;}
 #collection li button{float:right;}
 #collection li,#playlist li{list-style-type:none;}
+.template{display:none}
+
+#collection{display:none}/*hide the collection initially*/
+#collection li{padding-right:10px;}
+img.remove{float:right;};
index 7acb9f34c1b29acde795274065164f962c2862d6..94dd4d63605aca9eace7d7a1212ea8a566f6836d 100644 (file)
@@ -1,4 +1,4 @@
-#jp-interface{position:fixed;z-index:100;width:25em;left:201px;top:-20px;height:80px;border-bottom:none;}
+#jp-interface{position:fixed;z-index:100;width:25em;left:201px;top:-20px;height:60px;border-bottom:none;}
 #jp-interface div.player{height:0px}
 #jp-interface ul.jp-controls{list-style-type:none;padding:0;}
 #jp-interface ul.jp-controls li{display:inline;}
index 152c45834124ac6c9ea8efdd64ab2b40d03d0910..bd994e06341661568712533761b0c50148511da2 100644 (file)
@@ -33,18 +33,26 @@ if( !OC_User::isLoggedIn()){
 require_once('lib_collection.php');
 require_once('lib_scanner.php');
 
-OC_Util::addScript('media','player');
-OC_Util::addScript('media','music');
-OC_Util::addScript('media','jquery.jplayer.min');
-OC_Util::addStyle('media','player');
-OC_Util::addStyle('media','music');
+OC_UTIL::addScript('media','player');
+OC_UTIL::addScript('media','music');
+OC_UTIL::addScript('media','playlist');
+OC_UTIL::addScript('media','collection');
+OC_UTIL::addScript('media','jquery.jplayer.min');
+OC_UTIL::addStyle('media','player');
+OC_UTIL::addStyle('media','playlist');
+OC_UTIL::addStyle('media','music');
 
-OC_App::setActiveNavigationEntry( 'media_index' );
+OC_APP::setActiveNavigationEntry( 'media_playlist' );
 
 $tmpl = new OC_Template( 'media', 'music', 'user' );
 
-$player = new OC_Template( 'media', 'player');
+$player = new OC_TEMPLATE( 'media', 'player');
+$playlist = new OC_TEMPLATE( 'media', 'playlist');
+$collection= new OC_TEMPLATE( 'media', 'collection');
+
 $tmpl->assign('player',$player->fetchPage());
+$tmpl->assign('playlist',$playlist->fetchPage());
+$tmpl->assign('collection',$collection->fetchPage());
 $tmpl->printPage();
 ?>
  
diff --git a/apps/media/js/collection.js b/apps/media/js/collection.js
new file mode 100644 (file)
index 0000000..0a6e0e4
--- /dev/null
@@ -0,0 +1,126 @@
+Collection={
+       artists:[],
+       loaded:false,
+       loading:false,
+       loadedListeners:[],
+       load:function(ready){
+               if(ready){
+                       Collection.loadedListeners.push(ready);
+               }
+               if(!Collection.loading){
+                       Collection.loading=true;
+                       $.ajax({
+                               url: OC.linkTo('media','ajax/api.php')+'?action=get_collection',
+                               dataType: 'json',
+                               success: function(collection){
+                                       Collection.artists=collection;
+                                       
+                                       //set the album and artist fieds for the songs
+                                       for(var i=0;i<collection.length;i++){
+                                               var artist=collection[i];
+                                               for(var j=0;j<artist.albums.length;j++){
+                                                       var album=artist.albums[j]
+                                                       for(var w=0;w<album.songs.length;w++){
+                                                               album.songs[w].album_name=album.album_name;
+                                                               album.songs[w].artist_name=artist.artist_name;
+                                                               album.songs[w].artist_name=artist.artist_name;
+                                                       }
+                                               }
+                                       }
+                                       
+                                       Collection.loaded=true;
+                                       Collection.loading=false;
+                                       for(var i=0;i<Collection.loadedListeners.length;i++){
+                                               Collection.loadedListeners[i]();
+                                       }
+                                       
+                               }
+                       });
+               }
+       },
+       display:function(){
+               if(Collection.parent){
+                       Collection.parent.show();
+               }
+               if(!Collection.loaded){
+                       Collection.load(Collection.display)
+               }else{
+                       if(Collection.parent){
+                               Collection.parent.children('li.artist').remove();
+                               var template=Collection.parent.children('li.template');
+                               for(var i=0;i<Collection.artists.length;i++){
+                                       var artist=Collection.artists[i];
+                                       var li=template.clone();
+                                       li.data('artist',artist);
+                                       li.removeClass('template');
+                                       li.addClass('artist');
+                                       li.children('span').text(artist.artist_name);
+                                       li.children('button').click(function(){
+                                               PlayList.add($(this).parent().data('artist'));
+                                       })
+                                       Collection.parent.append(li);
+                               }
+                       }
+               }
+       },
+       parent:null,
+       hide:function(){
+               if(Collection.parent){
+                       Collection.parent.hide();
+               }
+       },
+       showAlbums:function(artistLi){
+               $('ul.albums').parent().removeClass('active');
+               $('ul.albums').remove();
+               var artist=artistLi.data('artist');
+               if(artist){
+                       var template=Collection.parent.children('li.template');
+                       var ul=$('<ul class="albums"></ul>');
+                       for(var i=0;i<artist.albums.length;i++){
+                               var li=template.clone();
+                               var album=artist.albums[i];
+                               li.removeClass('template');
+                               li.addClass('album');
+                               li.data('album',album);
+                               li.children('span').text(album.album_name);
+                               li.children('button').click(function(){
+                                       PlayList.add($(this).parent().data('album'));
+                               })
+                               ul.append(li);
+                       }
+                       artistLi.append(ul);
+               }
+       },
+       showSongs:function(albumLi){
+               $('ul.songs').parent().removeClass('active');
+               $('ul.songs').remove();
+               var album=albumLi.data('album');
+               var template=Collection.parent.children('li.template');
+               var ul=$('<ul class="songs"></ul>');
+               for(var i=0;i<album.songs.length;i++){
+                       var li=template.clone();
+                       var song=album.songs[i];
+                       li.removeClass('template');
+                       li.addClass('song',song);
+                       li.children('span').text(song.song_name);
+                       li.children('button').click(function(){
+                               PlayList.add($(this).parent().data('span'));
+                       })
+                       ul.append(li);
+               }
+               albumLi.append(ul);
+       }
+}
+
+$(document).ready(function(){
+       Collection.parent=$('#collection');
+       Collection.load();
+       $('#collection li.artist>span').live('click',function(){
+               $(this).parent().toggleClass('active');
+               Collection.showAlbums($(this).parent());
+       });
+       $('#collection li.album>span').live('click',function(){
+               $(this).parent().toggleClass('active');
+               Collection.showSongs($(this).parent());
+       });
+});
index ba34e66c3bad33dfe9919027e7165f9e573e6253..d43b260d1ebd0259a534262afe2b3bbe5f82ac16 100644 (file)
@@ -1,85 +1,20 @@
 $(document).ready(function(){
        //load the collection
-       $.ajax({
-               url: OC.linkTo('media','ajax/api.php')+'?action=get_collection',
-               dataType: 'json',
-               success: function(collection){
-                       displayCollection(collection);
-               }
+       $('#plugins a[href="#collection"]').click(function(){
+               $('#plugins li.subentry a.active').removeClass('active');
+               $(this).addClass('active');
+               PlayList.hide();
+               Collection.display();
+       });
+       $('#plugins a[href="#playlist"]').click(function(){
+               $('#plugins li.subentry a.active').removeClass('active');
+               $(this).addClass('active');
+               PlayList.render();
+               Collection.hide();
        });
 });
 
-function displayCollection(collection){
-       $('#collection').data('collection',collection);
-       $.each(collection,function(index,artist){
-               var artistNode=$('<li class="artist">'+artist.artist_name+'<button class="add">Add</button><ul/></li>');
-               artistNode.data('name',artist.artist_name);
-               artistNode.data('stuff',artist);
-               $('#collection>ul').append(artistNode);
-               $.each(artist.albums,function(index,album){
-                       var albumNode=$('<li class="album">'+album.album_name+'<button class="add">Add</button><ul/></li>');
-                       albumNode.data('name',album.album_name);
-                       albumNode.data('stuff',album);
-                       artistNode.children('ul').append(albumNode);
-                       $.each(album.songs,function(index,song){
-                               var songNode=$('<li class="song">'+song.song_name+'<button class="add">Add</button></li>');
-                               song.artist_name=artist.artist_name;
-                               song.album_name=album.album_name;
-                               songNode.data('name',song.song_name);
-                               songNode.data('stuff',song);
-                               albumNode.children('ul').append(songNode);
-                       });
-               });
-       });
-       $('li.album').hide();
-       $('li.song').hide();
-       $('li.artist').click(function(){
-               $(this).children().children().slideToggle();
-               return false;
-       });
-       $('li.album').click(function(){
-               $(this).children().children().slideToggle();
-               return false;
-       });
-       $('li.song').click(function(){
-               return false;
-       });
-       $('li>button.add').click(function(){
-               PlayList.add($(this).parent().data('stuff'));
-               PlayList.render($('#playlist'));
-               return false;
-       });
-       if(window.location.href.indexOf('#')>-1){//autoplay passed arist/album/song
-               var vars=getUrlVars();
-               var play;
-               if(vars['artist']){
-                       $.each(collection,function(index,artist){
-                               if(artist.artist_name==vars['artist']){
-                                       play=artist;
-                                       if(vars['album']){
-                                               $.each(artist.albums,function(index,album){
-                                                       if(album.album_name==vars['album']){
-                                                               play=album;
-                                                               if(vars['song']){
-                                                                       $.each(album.songs,function(index,song){
-                                                                               if(song.song_name==vars['song']){
-                                                                                       play=song;
-                                                                               }
-                                                                       });
-                                                               }
-                                                       }
-                                               });
-                                       }
-                               }
-                       });
-               }
-               PlayList.add(play);
-               PlayList.play();
-       }else{
-               PlayList.init();
-       }
-       
-}
+
 
 function getUrlVars(){
        var vars = [], hash;
@@ -94,7 +29,7 @@ function getUrlVars(){
 }
 
 function musicTypeFromFile(file){
-       var extention=file.substr(file.indexOf('.')+1);
+       var extention=file.split('.').pop();
        if(extention=='ogg'){
                return 'oga'
        }
index f76628110a841f4515e694959ab8e8dbcee8c5ed..7beb01b6013ad600f9c8ec446d385c931bf10f4c 100644 (file)
@@ -3,13 +3,13 @@ var PlayList={
        current:-1,
        items:[],
        player:null,
-       parent:null,
        next:function(){
                var next=PlayList.current+1;
                if(next>=PlayList.items.length){
                        next=0;
                }
                PlayList.play(next);
+               PlayList.render();
        },
        previous:function(){
                var next=PlayList.current-1;
@@ -17,6 +17,7 @@ var PlayList={
                        next=PlayList.items.length-1;
                }
                PlayList.play(next);
+               PlayList.render();
        },
        play:function(index){
                if(index==null){
@@ -66,6 +67,9 @@ var PlayList={
                });
        },
        add:function(song){
+               if(!song){
+                       return;
+               }
                if(song.substr){//we are passed a string, asume it's a url to a song
                        PlayList.addFile(song);
                }
@@ -81,7 +85,7 @@ var PlayList={
                }
                if(song.song_name){
                        var type=musicTypeFromFile(song.song_path);
-                       var item={name:song.song_name,type:type,artist:song.artist_name,album:song.album_name};
+                       var item={name:song.song_name,type:type,artist:song.artist_name,album:song.album_name,length:song.song_length,playcount:song.song_playcount};
                        item[type]=PlayList.urlBase+encodeURIComponent(song.song_path);
                        PlayList.items.push(item);
                }
@@ -92,30 +96,9 @@ var PlayList={
                item[type]=PlayList.urlBase+encodeURIComponent(path);
                PlayList.items.push(item);
        },
-       render:function(parent){//parent should be an ul element
-               if(parent){
-                       PlayList.parent=parent;
-               }else{
-                       parent=PlayList.parent;
-               }
-               if(parent){
-                       parent.empty();
-                       for(var i=0;i<PlayList.items.length;i++){
-                               var song=PlayList.items[i];
-                               var item=$('<li>'+song.artist+' - '+song.album+' - '+song.name+'</li>');
-                               item.data('artist',song.artist);
-                               item.data('album',song.album);
-                               item.data('name',song.name);
-                               item.data('index',i);
-                               item.click(function(){
-                                       PlayList.play($(this).data('index'));
-                                       PlayList.render();
-                               });
-                               if(i==PlayList.current){
-                                       item.addClass('current');
-                               }
-                               parent.append(item);
-                       }
-               }
-       }
+       remove:function(index){
+               PlayList.items.splice(index,1);
+               PlayList.render();
+       },
+       render:function(){}
 }
diff --git a/apps/media/js/playlist.js b/apps/media/js/playlist.js
new file mode 100644 (file)
index 0000000..570e725
--- /dev/null
@@ -0,0 +1,142 @@
+PlayList.render=function(){
+       $('#playlist').show();
+       PlayList.parent.empty();
+       for(var i=0;i<PlayList.items.length;i++){
+               var tr=PlayList.template.clone();
+               var item=PlayList.items[i];
+               if(i==PlayList.current){
+                       tr.addClass('current');
+               }
+               tr.removeClass('template');
+               tr.data('name',item.name);
+               tr.data('artist',item.artist);
+               tr.data('album',item.album);
+               tr.data('time',item.length);
+               tr.data('plays',item.playcount);
+               tr.children('td.name').children('span').text(item.name);
+               tr.children('td.artist').text(item.artist);
+               tr.children('td.album').text(item.album);
+               var secconds=(item.length%60);
+               if(secconds<10){
+                       secconds='0'+secconds;
+               }
+               var length=Math.floor(item.length/60)+':'+secconds;
+               tr.children('td.time').text(length);
+               tr.children('td.plays').text(item.playcount);
+               tr.data('index',i);
+               tr.click(function(){
+                       PlayList.play($(this).data('index'));
+                       PlayList.parent.children('tr').removeClass('current');
+                       $(this).addClass('current');
+               });
+               tr.hover(function(){
+                       var button=$('<img class="remove" title="Remove"/>');
+                       button.attr('src',OC.imagePath('core','actions/delete'));
+                       $(this).children().last().append(button);
+                       button.click(function(event){
+                               event.stopPropagation();
+                               event.preventDefault();
+                               var index=$(this).parent().parent().data('index');
+                               PlayList.remove(index);
+                       });
+               },function(){
+                       $(this).children().last().children('img.remove').remove();
+               });
+               tr.children('td.name').children('input').click(function(event){
+                       event.stopPropagation();
+                       if($(this).attr('checked')){
+                               $(this).parent().parent().addClass('selected');
+                               if($('td.name input:checkbox').length==$('td.name input:checkbox:checked').length){
+                                       $('#selectAll').attr('checked',true);
+                               }
+                       }else{
+                               $(this).parent().parent().removeClass('selected');
+                               $('#selectAll').attr('checked',false);
+                       }
+                       procesSelection();
+               });
+               PlayList.parent.append(tr);
+       }
+}
+PlayList.getSelected=function(){
+       return $('td.name input:checkbox:checked').parent().parent();
+}
+PlayList.hide=function(){
+       $('#playlist').hide();
+}
+
+$(document).ready(function(){
+       PlayList.parent=$('#playlist tbody');
+       PlayList.template=$('#playlist tr.template');
+       $('#selectAll').click(function(){
+               if($(this).attr('checked')){
+                       // Check all
+                       $('td.name input:checkbox').attr('checked', true);
+                       $('td.name input:checkbox').parent().parent().addClass('selected');
+               }else{
+                       // Uncheck all
+                       $('td.name input:checkbox').attr('checked', false);
+                       $('td.name input:checkbox').parent().parent().removeClass('selected');
+               }
+               procesSelection();
+       });
+});
+
+function procesSelection(){
+       var selected=PlayList.getSelected();
+       if(selected.length==0){
+               $('th.name span').text('Name');
+               $('th.artist').text('Artist');
+               $('th.album').text('Album');
+               $('th.time').text('Time');
+               $('th.plays').empty();
+               $('th.plays').text('Plays');
+       }else{
+               var name=selected.length+' selected';
+               var artist=$(selected[0]).data('artist');
+               var album=$(selected[0]).data('album');
+               var time=$(selected[0]).data('time');
+               var plays=$(selected[0]).data('plays');
+               for(var i=1;i<selected.length;i++){
+                       var item=$(selected[i]);
+                       if(artist!='mixed' && item.data('artist')!==artist){
+                               artist='mixed'
+                       }
+                       if(album!='mixed' && item.data('album')!==album){
+                               album='mixed'
+                       }
+                       if(time!='mixed' && item.data('time')!==time){
+                               time='mixed'
+                       }
+                       if(plays!='mixed' && item.data('plays')!==plays){
+                               plays='mixed'
+                       }
+               }
+               $('th.name span').text(name);
+               $('th.artist').text(artist);
+               $('th.album').text(album);
+               if(time!='mixed'){
+                       var secconds=(time%60);
+                       if(secconds<10){
+                               secconds='0'+secconds;
+                       }
+                       var time=Math.floor(time/60)+':'+secconds;
+               }
+               $('th.time').text(time);
+               $('th.plays').text(plays);
+               var button=$('<img class="remove" title="Remove"/>');
+               button.attr('src',OC.imagePath('core','actions/delete'));
+               $('th.plays').append(button);
+               button.click(function(event){
+                       event.stopPropagation();
+                       event.preventDefault();
+                       PlayList.getSelected().each(function(index,element){
+                               var index=$(element).data('index');
+                               PlayList.items[index]=null;
+                       });
+                       PlayList.items=PlayList.items.filter(function(item){return item!==null});
+                       PlayList.render();
+                       procesSelection();
+               });
+       }
+}
\ No newline at end of file
index d9f567aa68210f9f60a08aff6153ccaef2fb2d4e..5a16aaee8485cbd2a9fe4eda0c69d255e66d11fc 100644 (file)
@@ -128,7 +128,7 @@ class OC_MEDIA_COLLECTION{
                $artists=$query->execute(array($search,OC_User::getUser()))->fetchAll();
                $result=array();
                foreach($artists as $artist){
-                       $result[$artist['id']]=array('artist_name'=>$artist['name'],'artist_id'=>$artist['id']);
+                       $result[]=array('artist_name'=>$artist['name'],'artist_id'=>$artist['id']);
                }
                return $result;
        }
@@ -179,7 +179,7 @@ class OC_MEDIA_COLLECTION{
                $result=array();
                foreach($albums as $album){
                        if(count(self::getSongs($album['album_artist'],$album['album_id']))){
-                               $result[$album['album_id']]=$album;
+                               $result[]=$album;
                        }
                }
                return $result;
diff --git a/apps/media/templates/collection.php b/apps/media/templates/collection.php
new file mode 100644 (file)
index 0000000..e132eea
--- /dev/null
@@ -0,0 +1,9 @@
+<ul id='collection'>
+       <li class='artist'>
+               <img src="<?php echo image_path('files','loading.gif') ?>" alt='loading'/>Loading Collection...
+       </li>
+       <li class='template'>
+               <span></span>
+               <button>Add</button>
+       </li>
+</ul>
\ No newline at end of file
index 47ad64fa7c6bb050369b7a1a7853ddf5d7630203..7a61d59c9ba8421d7565eda6a11f670949c2b753 100644 (file)
@@ -1,3 +1,3 @@
 <?php echo $_['player'];?>
-<div id='collection'><ul/></div>
-<ul id="playlist"/>
\ No newline at end of file
+<?php echo $_['collection'];?>
+<?php echo $_['playlist'];?>
diff --git a/apps/media/templates/playlist.php b/apps/media/templates/playlist.php
new file mode 100644 (file)
index 0000000..bdc6ef5
--- /dev/null
@@ -0,0 +1,30 @@
+<table id='playlist'>
+       <thead>
+               <tr>
+                       <th class='name'><input id='selectAll' type='checkbox'>Name</th>
+                       <th class='artist'>Artist</th>
+                       <th class='album'>Album</th>
+                       <th class='time'>Time</th>
+                       <th class='plays'>Plays</th>
+               </tr>
+       </thead>
+       <tbody>
+               <tr>
+                       <td>
+                               The playlist is empty
+                       </td>
+               </tr>
+       </tbody>
+       <tfoot>
+               <tr class='template'>
+                       <td class='name'>
+                               <input type='checkbox'>
+                               <span></span>
+                       </td>
+                       <td class='artist'></td>
+                       <td class='album'></td>
+                       <td class='time'></td>
+                       <td class='plays'></td>
+               </tr>
+       </tfoot>
+</table>
\ No newline at end of file