Index
Audio-Gallery-Suite is a complete audio gallery suite/solution that includes a web audio gallery and a software for managing playlists, uploading audios and managing the web audio gallery. The Audio-Gallery-Suite is built up using the latest technology and harnesses their power to put up a sophisticated solution. The Audio-Gallery-Suite is made in a way to be easily setup and deployed by anyone who wishes to use it. I hope that this project proves useful for anyone who intends to use it.
View the web audio gallery at the link given below. I would recommend Chrome/Firefox web browser to view the web audio gallery. http://projects.robinrizvi.info/audio-gallery-suite/
Download the software at the link given below for uploading audios and managing the web audio gallery used in this demonstration. Use the username [superuser] and password [superuser] without the square brackets to log into the software.
http://projects.robinrizvi.info/audio-gallery-suite/software/audiogallery_setup.zip
Download the complete Audio-Gallery-Suite along with the complete source code and all the resources used in the Audio-Gallery-Suite. If you want to implement the Audio-Gallery-Suite in any of your projects, you will have to download the full suite and follow
the setup instructions that I discuss later. You can contribute to this project and make it more better, feel free to contribute by forking the project on Codeplex using the same link given below.
http://audiogallerysuite.codeplex.com/
I will present the inner workings of how the audio-gallery-suite works (in brief). It is recommended that you see the Demonstration section (see above) before plunging into understanding the code and the inner workings. I will discuss about the implementation of some of key features of the audio-gallery-suite (web gallery and gallery software) and try to give you a brief insight on the code used for implementing those features.
Before getting to the code, let us see how the whole thing works. Lets get an abstract idea of the main parts of the suite and how they all fit together. The main parts of the suite are:
Let us consider a scenario to understand how the audio-gallery-suite works and how the above said four parts fit in together (magically, I wished!).
After coming this far, lets walk a few more steps together. As mentioned above, the four main parts of the Audio-Gallery-Suite are : [audiogallery] directory, database, web audio gallery and audio gallery software. These four main parts are discussed in more detail in the sections below (in the stated sequence).
Let us take a look at the structure of the directory where the audios will be uploaded. This directory is named [audiogallery]. Since this directory contains the playlists and the audios that are uploaded, and it used by both the audio gallery software and the web audio gallery. It has a certain format of directory structure which is like this: {Root}/audiogallery/user_id/playlist_id/audios/
The database schema presented in the above diagram shows the four tables used in the database.
audio : The audio table contains the audios that are uploaded. It specifies the id, name, title and description of the audio. It has a attribute [playlist_id] which specifies the playlist to which a particular audio belongs.
playlist : The playlist table contains the paylists that are created. It specifies the id, name, thumb (thumbnail representing the playlist) and description of the playlist. It has an attribute [user_id] which specifies the user which created a particular playlist and has the authority to manage that playlist.
user : The user table contains all the users that are authorized to use the audio gallery software. It specifies the id, username, password, name and description of a particular user. The authorized users can create playlists and upload audios. The audios uploaded by the user will belong to one of the playlists creating by him.
settings : The settings table only contains a single record about the FTP information. This FTP information is used by the audio gallery software to upload audios to the server/host.
The main file of the web gallery is the [index.html] file. It is structured as shown in the above diagram. Visually, it is divided into two parts the left and the right part. (See the Screens section below for more clarification)
The left part contains playlist selection element, audio list and audio search element.
The right part contains the coverart (the DVD case), audio player and currently playing audio name and playlist name.
Here is the screenshot showing the main [index.html]
The original HTML code and structuring of the [index.html] can be seen below.
<body>
<!--Contains the album selector, audios from the playlist and search-->
<div id="left">
<div id="playlistselecttoolbar">
<label>Select Playlist :</label> <select name="playlistselect" id="playlistselect">
<!--Content will be loaded via ajax php from the database-->
</select>
</div>
<div id="audiolist">
<!--Content will be loaded via ajax php from the database.-->
</div>
<div id="searchaudiotoolbar">
<label>Search Audio :</label> <input id="searchinput" name="audiosearchtxt" type=
"text" />
<div id="searchbtnimg"></div>
</div>
</div>
<!--Contains the audio player, cover art and hopefully a visualizer if I can pull it off-->
<div id="right">
<div id="coverart">
<img src="image/audiogallery_images/coverart.png" oriwidth="693" oriheight="680" width="693" height="680" alt="coverart" />
<img id="rotatingdisc" src="image/audiogallery_images/rotating_disc.png" width="350" height="350" alt="rotatingdisc" name="rotatingdisc" />
</div>
<div class="sharebar">
<input name="audiourl" type="text" readonly="true" />
<!--<a id="copyurltool" class="button gray medium">COPY</a>-->
</div>
<div id="toolbar">
<a id="shareaudiotool" class="sharebar-trigger button gray medium" name="shareaudiotool">LINK</a>
<a id="addthissharetool" class="sharebar-trigger button gray medium" name="addthissharetool">SHARE</a>
<a id="downloadaudiotool" target="_blank" class="button gray medium" name="downloadaudiotool">DOWNLOAD</a>
<a id="helptool" class="button gray medium" rel="prettyPhoto[inline]" href="#inline_content" name="helptool">HELP</a>
</div>
<div id="player">
<div id="jquery_jplayer_2" class="jp-jplayer"></div>
<div class="jp-audio">
<div class="jp-type-playlist">
<div id="jp_interface_2" class="jp-interface">
<ul class="jp-controls">
<li><a href="#" class="jp-play" tabindex="1">play</a></li>
<li><a href="#" class="jp-pause" tabindex="1">pause</a></li>
<li><a href="#" class="jp-stop" tabindex="1">stop</a></li>
<li><a href="#" class="jp-mute" tabindex="1">mute</a></li>
<li><a href="#" class="jp-unmute" tabindex="1">unmute</a></li>
<li><a href="#" class="jp-previous" tabindex="1">previous</a></li>
<li><a href="#" class="jp-next" tabindex="1">next</a></li>
</ul>
<div class="jp-progress">
<div class="jp-seek-bar">
<div class="jp-play-bar"></div>
</div>
</div>
<div class="jp-volume-bar">
<div class="jp-volume-bar-value"></div>
</div>
<div class="jp-current-time"></div>
<div class="jp-duration"></div>
</div>
</div>
</div>
</div>
<div id="playerstatbar">
<span id="nowplaying">Now Playing:</span> <span id="currentplaylist">Current
Playlist: </span>
</div>
</div><!--This is an isolated div to show help to the users-->
<div style='display:none'>
<div id='inline_content'>
<div style=
"background-color:#000;padding:5px;width:98%; margin-bottom:5px;text-align:center;color:#FFF;height:1em;font-size:1em;">
HELP/INSTRUCTIONS
</div>
<div style='padding:10px; vertical-align:middle; font-size:0.9em;'>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/mouse.png" width=
"50" height="70" /><span style='margin-left:10px;'>Use your mouse to interact
with the player.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/space.png" width=
"150" height="50" /><span style='margin-left:10px;'>Press the space-bar to
toggle Play and Pause.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/right.png" width=
"50" height="50" /><span style='margin-left:10px;'>Press the right arrow key to
jump to next audio.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/left.png" width="50"
height="50" /><span style='margin-left:10px;'>Press the left arrow key to jump
to previous audio.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/m.png" width="50"
height="50" /><span style='margin-left:10px;'>Press the M key to toggle mute
and unmute.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/shift.png" width=
"50" height="50" /> + <img style='vertical-align:middle;' src=
"image/help_images/right.png" width="50" height="50" /><span style=
'margin-left:10px;'>Use Shift + Right arrow key to seek forward.</span>
</div>
<div style="margin-bottom:15px">
<img style='vertical-align:middle;' src="image/help_images/shift.png" width=
"50" height="50" /> + <img style='vertical-align:middle;' src=
"image/help_images/left.png" width="50" height="50" /><span style=
'margin-left:10px;'>Use Shift + Left arrow key to seek backward.</span>
</div>
</div>
<div style=
"background-color:#000;padding:5px;width:98%; margin-bottom:5px;text-align:center;color:#FFF;height:1em;font-size:1em;">
<input name="popupstartup" id="popupstartupcheck" onchange="showpopupchange();"
type="checkbox" value="" /> Do not show at start up!
</div>
</div>
</div>
</body>
[index.html]
After seeing the basic skeleton and HTML, lets move on to see the basic features/functionality of web audio gallery and how they are implemented.
After the DOM gets ready i.e in the $(document).ready(function(){---});, an ajax request is made to ]audiogalleryengine.php] with data (playlistselct:1). After receiving the data the [audiogalleryengine.php] queries the database for all playlists (select * from playlist) and for each playlist record it forms <option> elements.
Lets see a screenshot after the playlists have been loaded:
Now let us see the code from the different files that make it happen:
<select name="playlistselect" id="playlistselect">
<!--Content will be loaded via ajax php from the database-->
</select>
[index.html]
//Filling the playlist select box with values via XHR/ajax
$("#playlistselect").load("php/audiogalleryengine.php",{playlistselect:1},function(){$("#playlistselect").change();$('select').selectmenu({style:'dropdown'});});
[script_audio.js]
//for getting all the playlist and returning them
if (isset($_POST['playlistselect']))
{
$query="SELECT * FROM playlist";
$result=$mysqli->query($query);
$responsehtml='<option value="0">All</option>';
while($tuple=$result->fetch_array())
{
$responsehtml.="<option value={$tuple['id']}>{$tuple['name']}</option>";
}
$result->close();
echo $responsehtml;
exit();
}
[audiogalleryengine.php]
Audios are loaded when a playlist is changed or the user does an audio search. Depending upon the value of playlist select box(dropdown) and the search text an ajax call is made to [audiogalleryengine.php] with data (playlistid and searchtext). Based on the data received the [audiogalleryengine.php] queries the database (audio table) for all the audios belonging to the particular playlist and similar to the search text. After receiving the audio records from the database the [audiogalleryengine.php] forms <li> elements for all audios.
Lets see a screenshot after the audios have been loaded:
Now let us see the code from the different files that make it happen:
<div id="audiolist">
<!--Content will be loaded via ajax php from the database.-->
</div>
[index.html]
//Event handler for playlistselect change
$("#playlistselect").change(function(){
searchaudio();
$("#currentplaylist span").text($("#playlistselect :selected").text());
});
//Initializing the audio search button
$("#searchbtnimg").click(function(){
searchaudio();
});
//Calling search audio function each time when user presses a key
$("#searchinput").keyup(function(){
searchaudio();
});
function searchaudio()
{
searchxhrabort();
$("#audiolist").showLoading();
window.searchxhr = $.ajax({
type: "POST",
url: "php/audiogalleryengine.php",
data: "playlistid="+$('#playlistselect').val()+"&searchtext="+$('#searchinput').val(),
success: function(html){
//Adding the ajax returned audiotracks to the audiolist and reinitializing jscrollpane
window.scrollableaudiolist.html(html);
window.jscrollapi.reinitialise();
$("#audiolist").hideLoading();
//Storing previous currentplaytime beforing creating a new playlist from the new search items because after creating the new playlist the currentplaytime would automatically get to zero so that I can seek properly if the old element is found in the new search
$("#player").data("previouscurrenttime",$("#player").data("currenttime"));
//Reinitializing the playlist for jplayer
createplaylist();
//Checking if the current playing track in the old playlist is also present in the new search list and taking actions accordingly
var trackidifany=currenttrackinnewsearch();
if (trackidifany===false)
{
window.audioPlaylist.playlistChange(0);
$("#jquery_jplayer_2").jPlayer("stop");
}
else
{
window.audioPlaylist.playlistChange(trackidifany);
$("#jquery_jplayer_2").jPlayer("pause",$("#player").data("previouscurrenttime"));
}
//Adding event hander for the newly loaded audiotracks
$(".audiotrack").click(function(){
var audiotrackindex=$(this).index();
window.audioPlaylist.playlistChange(audiotrackindex);
});
}
});
}
[script_audio.js]
if (isset($_POST['playlistid']) && isset($_POST['searchtext']))
{
$playlistid=$_POST['playlistid'];
$searchtext=$_POST['searchtext'];
if ($searchtext=="") $searchtext="%"; else $searchtext="%".$searchtext."%";
if ($playlistid==0) $query="SELECT audio.name AS name,audio.title AS title,audio.playlist_id AS playlistid,playlist.user_id AS userid FROM (audio JOIN playlist ON audio.playlist_id=playlist.id) WHERE audio.title LIKE '{$searchtext}'";
else $query="SELECT audio.name AS name,audio.title AS title,audio.playlist_id AS playlistid,playlist.user_id AS userid FROM (audio JOIN playlist ON audio.playlist_id=playlist.id) WHERE audio.playlist_id={$playlistid} AND audio.title LIKE '{$searchtext}'";
$result=$mysqli->query($query);
$html="<ul>";
while($tuple=$result->fetch_array())
{
$userid=$tuple['userid'];
$playlistid=$tuple['playlistid'];
$name=$tuple['name'];
$title=$tuple['title'];
$audiourl="{$root}audiogallery/user_{$userid}/playlist_{$playlistid}/audios/{$name}";
$html.="<li class=\"audiotrack\" url=\"{$audiourl}\">{$title}</li>";
}
$html.="</ul>";
echo $html;
}
[audiogalleryengine.php]
The audio player in audio-gallery-suite uses Jplayer for playback and maintaining playlist information. But a slight catch is that it is well documented for (or say only made for) static playlist, so I had to use some tricks to feed the playlist data to Jplayer’s playlist function so everything works smoothly. I created an object string like this: var myaudioplaylist=[{name:"title",mp3:"url"},{..},..] and executed the eval() javascript function on this string which created a object named myaudioplaylist and I passed this into the Jplayer Playlist constructor. See the code for clear understanding. All this functionality is encapsulated into a createplaylist() function which is called when the user selects a new playlist from the playlist select dropdown.
Now let us see the code from the different files that make it happen:
function createplaylist()
{
var objectarraystring="var myaudioplaylist=[";
$(".audiotrack").each(function(){
objectarraystring=objectarraystring+"{name:\""+$(this).text()+"\",mp3:\""+$(this).attr('url')+"\"},";
});
objectarraystring+="]";
eval(objectarraystring);
audioPlaylist = new window.Playlist("2",myaudioplaylist, {
ready: function() {
audioPlaylist.playlistInit(false); // Parameter is a boolean for autoplay.
},
ended: function() {
audioPlaylist.playlistNext();
},
play: function() {
$(this).jPlayer("pauseOthers");
},
swfPath: "flash",
supplied: "mp3",
solution: "html, flash",
wmode: "window",
preload: "auto"
});
}
[script_audio.js]
The audio is played and controlled by Jplayer (a Jquery plugin) and hence it is implemented using the Jplayer standards/techniques. Visit http://jplayer.org/ for details regarding this.
Lets see a screenshot of the (custom) Jplayer interface on the web gallery:
Now let us see the code from the different files that make it happen:
<div id="player">
<div id="jquery_jplayer_2" class="jp-jplayer"></div>
<div class="jp-audio">
<div class="jp-type-playlist">
<div id="jp_interface_2" class="jp-interface">
<ul class="jp-controls">
<li><a href="#" class="jp-play" tabindex="1">play</a></li>
<li><a href="#" class="jp-pause" tabindex="1">pause</a></li>
<li><a href="#" class="jp-stop" tabindex="1">stop</a></li>
<li><a href="#" class="jp-mute" tabindex="1">mute</a></li>
<li><a href="#" class="jp-unmute" tabindex="1">unmute</a></li>
<li><a href="#" class="jp-previous" tabindex="1">previous</a></li>
<li><a href="#" class="jp-next" tabindex="1">next</a></li>
</ul>
<div class="jp-progress">
<div class="jp-seek-bar">
<div class="jp-play-bar"></div>
</div>
</div>
<div class="jp-volume-bar">
<div class="jp-volume-bar-value"></div>
</div>
<div class="jp-current-time"></div>
<div class="jp-duration"></div>
</div>
</div>
</div>
</div>
[index.html]
$(document).ready(function(){
//Other code here see source files, removed it for a clearer view
//Global variables for playlist management
var Playlist;
var audioPlaylist;
/*Defines Jplayer Playlist Class*/
initjplayerplaylistfunctions();
});
function initjplayerplaylistfunctions()
{
Playlist = function(instance, playlist, options) {
var self = this;
this.instance = instance; // String: To associate specific HTML with this playlist
this.playlist = playlist; // Array of Objects: The playlist
this.options = options; // Object: The jPlayer constructor options for this playlist
this.current = 0;
this.cssId = {
jPlayer: "jquery_jplayer_",
interface: "jp_interface_",
playlist: "jp_playlist_"
};
this.cssSelector = {};
$.each(this.cssId, function(entity, id) {
self.cssSelector[entity] = "#" + id + self.instance;
});
if(!this.options.cssSelectorAncestor) {
this.options.cssSelectorAncestor = this.cssSelector.interface;
}
$(this.cssSelector.jPlayer).jPlayer(this.options);
$(this.cssSelector.interface + " .jp-previous").click(function() {
self.playlistPrev();
$(this).blur();
return false;
});
$(this.cssSelector.interface + " .jp-next").click(function() {
self.playlistNext();
$(this).blur();
return false;
});
};
Playlist.prototype = {
playlistInit: function(autoplay) {
if(autoplay) {
this.playlistChange(this.current);
} else {
this.playlistConfig(this.current);
}
},
playlistConfig: function(index) {
$(this.cssSelector.playlist + "_item_" + this.current).removeClass("jp-playlist-current").parent().removeClass("jp-playlist-current");
$(this.cssSelector.playlist + "_item_" + index).addClass("jp-playlist-current").parent().addClass("jp-playlist-current");
this.current = index;
$(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
$("#nowplaying span").text(this.playlist[this.current].name);
$("#player").data("currenttrackurl",this.playlist[this.current].mp3);
playlistuiupdate(1,$('#player').data("currenttrackindex"),index);
$('#player').data("currenttrackindex",index);
shareupdate($("#player").data("currenttrackurl"),this.playlist[this.current].name);
},
playlistChange: function(index) {
this.playlistConfig(index);
$(this.cssSelector.jPlayer).jPlayer("play");
},
playlistNext: function() {
var index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0;
this.playlistChange(index);
},
playlistPrev: function() {
var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1;
this.playlistChange(index);
}
};
}
//The createplaylist() function is called from the searchaudio() function which in turn is called when the playlist is changed or audio search is done. So when new audiolist is formed, new playlist is initialized for it and new instance of Jplayer is created for it.
function createplaylist()
{
var objectarraystring="var myaudioplaylist=[";
$(".audiotrack").each(function(){
objectarraystring=objectarraystring+"{name:\""+$(this).text()+"\",mp3:\""+$(this).attr('url')+"\"},";
});
objectarraystring+="]";
eval(objectarraystring);
audioPlaylist = new window.Playlist("2",myaudioplaylist, {
ready: function() {
audioPlaylist.playlistInit(false); // Parameter is a boolean for autoplay.
},
ended: function() {
audioPlaylist.playlistNext();
},
play: function() {
$(this).jPlayer("pauseOthers");
},
swfPath: "flash",
supplied: "mp3",
solution: "html, flash",
wmode: "window",
preload: "auto"
});
}
[script_audio.js]
The rotating disc is a transparent png image [rotating_disc.png]. This image is placed appropriately (by doing some calculations on aspect ratio etc) on the coverart image [coverart.png]. After placing the rotating disc, when the audio is played the disc is rotated using CSS3's transform property transform:rotate(angle); Due to inconsistencies in the different browsers in implementing the CSS transform property (-ms-transform,-moz-transform,-webkit-transform,-o-transform), we have to use the appropriate name for the property depending on the browser, so for doing this the easy way I used the jquery-animate-css-rotate-scale jquery library for acheiving our motive.
Lets see a screenshot of the rotating disc as seen in the web gallery:
Now let us see the code from the different files that make it happen:
<div id="coverart">
<img src="image/audiogallery_images/coverart.png " oriwidth="693" oriheight="680" width="693" height="680" alt="coverart" />
<img id="rotatingdisc" src="image/audiogallery_images/rotating_disc.png" width="350" height="350" alt="rotatingdisc" />
</div>
[index.html]
$(document).ready(function(){
//Other code is present here, see source files. Removed for clarity
//adding audioplaying status data to my player and starting the disc
$("#jquery_jplayer_2").bind($.jPlayer.event.play, function() {
$("#player").data("audioplaying",true);
startdisk();
});
//adding audioplaying status data to my player and stopping the disc
$("#jquery_jplayer_2").bind($.jPlayer.event.pause, function() {
$("#player").data("audioplaying",false);
stopdisk();
});
});
function setdimensions()
{
//Other code is present here, see source files. Removed for clarity
// Making the coverart rotatingdisc to the correct size and positioning it
$("#rotatingdisc").width($("#coverart img").width()/1.98);
$("#rotatingdisc").height($("#rotatingdisc").width());
var rotatingdiscleftposition=$("#coverart img").position().left+$("#coverart img").width()/2.240896;
var rotatingdisctopposition=$("#coverart img").position().top+$("#coverart img").height()/4.65753;
$("#rotatingdisc").css('left',rotatingdiscleftposition);
$("#rotatingdisc").css('top',rotatingdisctopposition);
//rotatingdisc code end
}
//Rotate disk at regular intervals by specified angle
function rotatedisk(element,angle)
{
stopdisk();
element.stop().animate({rotate: '+=150deg'}, 800, 'easeInCubic', function() {
var intervalid = setInterval(
function () {
element.animate({rotate: '+=' + angle + 'deg'}, 0);
},
25
);
element.data('intervalid', intervalid);
});
}
function stopdisk()
{
var intervalid = $('#rotatingdisc').data('intervalid');
clearInterval(intervalid);
$('#rotatingdisc').stop().animate({rotate: '+=150deg'}, 800, 'easeOutCubic');
}
function startdisk()
{
var randnum=Math.floor(Math.random() * (60 - 10 + 1) + 10);//remove this if it doesn't suite you
rotatedisk($('#rotatingdisc'),randnum);
}
[script_audio.js]
The audio player of the web gallery can also be controlled by keyboard keys. This gives users of the web gallery easy accessibility and control over the player. I have used keydown event handler to detect which key is pressed and depending on the key, an action is performed. The binding of keydown event and its event handler is encapsulauted in a function called [initplayerkeyboardsupport] which is called when DOM is ready i.e (in the $(document).ready(function(){---}); )
Lets see a screenshot showing the keys that can be used to control the player:
Now let us see the code that makes it happen:
function initplayerkeyboardsupport()
{
$(document).keydown(function(event)
{
//for seeking
if (event.shiftKey)
{
switch (event.keyCode)
{
case 37: // SHIFT + left arrow
$("#player").data("currenttime")-1>=0?$("#jquery_jplayer_2").jPlayer("playHead", $("#player").data("currenttime")-1):null;
if (($("#player").data("currenttime")-1)>=0)
{
if ($("#player").data("audioplaying"))
{
$("#jquery_jplayer_2").jPlayer("play", $("#player").data("currenttime")-1);
$("#jquery_jplayer_2").jPlayer("play");
}
else
{
$("#jquery_jplayer_2").jPlayer("pause", $("#player").data("currenttime")-1);
}
}
return;
break;
case 39: // SHIFT + right arrow
if (($("#player").data("currenttime")+1)<=$("#player").data("currenttrackduration"))
{
if($("#player").data("audioplaying"))
{
$("#jquery_jplayer_2").jPlayer("play", $("#player").data("currenttime")+1);
$("#jquery_jplayer_2").jPlayer("play");
}
else
{
$("#jquery_jplayer_2").jPlayer("pause", $("#player").data("currenttime")+1);
}
}
return;
break;
}
}
switch (event.keyCode)
{
case 37: // left arrow
window.audioPlaylist!=undefined?window.audioPlaylist.playlistPrev():null;
break;
case 39: // right arrow
window.audioPlaylist!=undefined?window.audioPlaylist.playlistNext():null;
break;
case 32: // space
if ($("#player").data("audioplaying")===false || $("#player").data("audioplaying")===undefined)
{
$("#jquery_jplayer_2").jPlayer("play");
$("#player").data("audioplaying",true);
}
else
{
$("#jquery_jplayer_2").jPlayer("pause");
$("#player").data("audioplaying",false);
}
break;
case 77: // m
if ($("#player").data("muted")===false || $("#player").data("muted")===undefined)
{
$("#jquery_jplayer_2").jPlayer("mute");
$("#player").data("muted",true);
}
else
{
$("#jquery_jplayer_2").jPlayer("unmute");
$("#player").data("muted",false);
}
break;
case 109: // M
if ($("#player").data("muted")===false || $("#player").data("muted")===undefined)
{
$("#jquery_jplayer_2").jPlayer("mute");
$("#player").data("muted",true);
}
else
{
$("#jquery_jplayer_2").jPlayer("unmute");
$("#player").data("muted",false);
}
break;
}
});
}
[script_audio.js]
Whenever the audio is changed, it is changed by the Jplayer "setmedia" method and this method is called in the playlistConfig function of the Playlist class (see the code below), so whenever the audio/media is changed using the Jplayer's setmedia method (http://jplayer.org/latest/developer-guide/#jPlayer-setMedia), just below the line where setmedia is called, I add the current track url information in a variable called [currenttrackurl] to our player div (#player) using the Jquery's data method (http://api.jquery.com/jQuery.data/). So by doing this I always have the currenttrackurl variable point to the currently playing track because whenever the audio is changed, the variable [currenttrackurl] is assigned a new/current value. So now in the download button click event handler I just get the value of the [currenttrackurl] variable and send that to [downloadengine.php] which in turn initiates the download of that particular file.
Lets see a screenshot of the download button as seen in the web gallery:
Now let us see the code from the different files that make it happen:
<a id="downloadaudiotool" target="_blank" class="button gray medium">DOWNLOAD</a>
[index.html]
$(document).ready(function(){
//Other code is present here, see source files. Removed for clarity
$("#downloadaudiotool").click(function(){
var filedownloadpath=encodeURIComponent($("#player").data("currenttrackurl"));
$("#downloadaudiotool").attr('href','php/downloadengine.php?file='+filedownloadpath);
});
});
function initjplayerplaylistfunctions()
{
Playlist = function(instance, playlist, options) {
//Other code is present here, see source files. Removed for clarity
playlistConfig: function(index) {
$(this.cssSelector.playlist + "_item_" + this.current).removeClass("jp-playlist-current").parent().removeClass("jp-playlist-current");
$(this.cssSelector.playlist + "_item_" + index).addClass("jp-playlist-current").parent().addClass("jp-playlist-current");
this.current = index;
$(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
$("#nowplaying span").text(this.playlist[this.current].name);
$("#player").data("currenttrackurl",this.playlist[this.current].mp3);
playlistuiupdate(1,$('#player').data("currenttrackindex"),index);
$('#player').data("currenttrackindex",index);
shareupdate($("#player").data("currenttrackurl"),this.playlist[this.current].name);
}
[script_audio.js]
Audio sharing uses the AddThis utility (http://www.addthis.com/) . As each new audio is set to play, I created a new instance of AddThis after removing the old instance from the DOM because AddThis is easy to implement and initialize with static content and mostly single elements, and here I wanted a single SHARE button for multiple audios which may exist dynamically over time, so I used this simple workaround to make it easy for me.
Lets see a screenshot of the share button as seen in the web gallery:
Now let us see the code that makes it happen:
function initjplayerplaylistfunctions()
{
Playlist = function(instance, playlist, options)
{
//Other code is present here, see source files. Removed for clarity
playlistConfig: function(index)
{
$(this.cssSelector.playlist + "_item_" + this.current).removeClass("jp-playlist-current").parent().removeClass("jp-playlist-current");
$(this.cssSelector.playlist + "_item_" + index).addClass("jp-playlist-current").parent().addClass("jp-playlist-current");
this.current = index;
$(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
$("#nowplaying span").text(this.playlist[this.current].name);
$("#player").data("currenttrackurl",this.playlist[this.current].mp3);
playlistuiupdate(1,$('#player').data("currenttrackindex"),index);
$('#player').data("currenttrackindex",index);
shareupdate($("#player").data("currenttrackurl"),this.playlist[this.current].name);
}
}
}
function shareupdate(url,title)
{
$('#addthissharetool').remove();
$('#toolbar').append('<a id="addthissharetool" class="sharebar-trigger button gray medium">SHARE</a>');
url=encodeURI(url);
addthis.button(
'#addthissharetool',
{
ui_click: true,
ui_open_windows:true,
},
{
url:url,
title:title,
description:'Visit robinrizvi.info'
}
);
//Create gray to blue hover effect for toolbar buttons
$("#addthissharetool").hover(function(){
$(this).removeClass('button gray medium').addClass('button blue medium');
},
function(){
$(this).removeClass('button blue medium').addClass('button gray medium');
}
);
}
[script_audio.js]
Let us see how some of the basic and intrinsic functionalities and features of the gallery management software are implemented in code. Most of the code in gallery management software is implemented in a way to support multithreaded architecture and to provide the user with a fast response and non-blocking UI experience. The code has a little flavor of the 3-tier architecture and as a result of that almost all of the code that is needed for database interaction resides in a static class called user [user.cs]. I will not be discussing the [user] class because it contains functions whose names are self explanatory as to what they do and you can always check the source files and read the comments therein or see the code for full understanding.
Lets see a screenshot giving the overview of the gallery software:
Now lets start with the discussion of the basic functionalities of the gallery software and their implementation.
After the user enters his credentials, these credentials are passed to the login() method of the user class [user.cs]. The user.login() method searches the [user] table in the database and if it finds a record with the given username and password, it sets the property/variable (static) named [isvalid] of the user class to true otherwise false. By examing the user.isvalid value it is determined whether the username and password entered is correct or not and appropriate action is taken accordingly (main form is shown if the username and password is correct).
Lets see a screenshot of the login form:
Now let us see the code that makes it happen:
private void loginbtn_Click(object sender, EventArgs e)
{
if (unametxt.Text != string.Empty && pwdtxt.Text != string.Empty)
{
userstatuspic.Visible = false;
loginloadingindicator.Visible = true;
loginloadingindicator.Active = true;
unametxt.Enabled = false;
pwdtxt.Enabled = false;
loginbtn.Enabled = false;
Thread loginthread = new Thread(user.login);
string[] unamepwd = { unametxt.Text, pwdtxt.Text };
loginthread.Start(unamepwd);
while (loginthread.IsAlive) Application.DoEvents();
if (user.isvalid)
{
userstatuspic.Image = Properties.Resources.user_accept;
userstatuspic.Visible = true;
loginloadingindicator.Visible = false;
loginloadingindicator.Active = false;
Application.DoEvents();
Thread.Sleep(2000);
main mainfrm = new main();
this.Hide();
mainfrm.ShowDialog();
this.Close();
}
else
{
userstatuspic.Image = Properties.Resources.user_remove;
unametxt.Enabled = true;
pwdtxt.Enabled = true;
loginbtn.Enabled = true;
userstatuspic.Visible = true;
loginloadingindicator.Visible = false;
loginloadingindicator.Active = false;
}
}
else MessageBox.Show("Please fill in all the fields.");
}
[login.cs]
As the main form is loaded, showplaylists() function is called which in turn calls the getplaylists() method of the user class [user.cs]. The user.getplaylists() method queries the playlist table in the database to get the playlists created by the user (which is currently is using the gallery software). The user.getplaylists() method stores the playlists returned by the database query into the user.playlists collection (see the code below)
public struct playlist
{
public UInt64 id;
public string name;
public string description;
public string thumb;
}
public static List<playlist> playlists = new List<playlist>();
public static void getplaylists()//reads the records from playlist table and for each entry calls downloadplaylist() to download the thumb and save it into local temp foler
{
playlists.Clear();
try
{
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "SELECT * FROM playlist WHERE user_id=@userid";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@userid", userid);
MySqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
playlist a1;
a1.id = UInt64.Parse(rdr["id"].ToString());
a1.name = rdr["name"].ToString();
a1.description = rdr["description"].ToString();
a1.thumb = rdr["thumb"].ToString();
if (downloadplaylistthumb(a1)) playlists.Add(a1);
}
conn.Close();
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("The connection to the database could not be made. Please check your internet connection." + Environment.NewLine+"The application will now exit.");
System.Windows.Forms.Application.Exit();
}
}
[user.cs]
After the playlists are stored in the user.playlists collection(List), then the control returns to the showplaylists() function and each playlist in the user.playlists collection is added to a Listview in the main form
private void showplaylists()
{
Thread getplaylistthread = new Thread(user.getplaylists);
getplaylistthread.Start();
while (getplaylistthread.IsAlive) Application.DoEvents();
playlistimagelist.Images.Clear();
playlistview.Items.Clear();
foreach (user.playlist playlist in user.playlists)
{
string filename = Application.StartupPath + "\\temp\\playlist_" + playlist.id + "\\" + playlist.thumb;
Bitmap img = new Bitmap(filename);
playlistimagelist.Images.Add(playlist.id.ToString(), img);
playlistview.Items.Add(playlist.name, playlist.id.ToString());
}
}
[main.cs]
When a playlist is selected (by clicking on it), the showaudios() function is called which in turn calls the getaudios() method of the user class [user.cs]. The user.getaudios() functions requires playlist_id as a parameter, depending on this parameter (playlist_id) it queries the audio table in the database for audios belonging to a particular playlist (whose playlist_id is given). The user.getaudios() method stores the audios returned by the database query into the user.audios collection (see the code below)
public struct audio
{
public UInt64 id;
public string name;
public string title;
public string description;
}
public static List<audio> audios = new List<audio>();
public static void getaudios(object data)//reads records from the audio table for a particular albumid and then saves the entries in the pictures list
{
Int32 currentplaylistid = (Int32)data;
audios.Clear();
try
{
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "SELECT * FROM audio WHERE playlist_id=@playlistid";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@playlistid", currentplaylistid);
MySqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
audio p1;
p1.id = UInt64.Parse(rdr["id"].ToString());
p1.name = rdr["name"].ToString();
p1.title = rdr["title"].ToString();
p1.description = rdr["description"].ToString();
if (downloadaudio(p1, currentplaylistid)) audios.Add(p1);//add the audios to the list
}
conn.Close();
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("The connection to the database could not be made. Please check your internet connection." + Environment.NewLine+"The application will now exit.");
System.Windows.Forms.Application.Exit();
}
}
[user.cs]
After the audios are stored in the user.audios collection(List), then the control returns to the showaudios() function and each audio in the user.audios collection is added to a Listview in the main form
private void showaudios(Int32 currentplaylistid)
{
Thread getaudiothread = new Thread(user.getaudios);
getaudiothread.Start(currentplaylistid);
while (getaudiothread.IsAlive) Application.DoEvents();
audioimagelist.Images.Clear();
audiolistview.Items.Clear();
foreach (user.audio audio in user.audios)
{
audioimagelist.Images.Add(audio.id.ToString(), Properties.Resources.audio);
audiolistview.Items.Add(audio.title, audio.id.ToString());
}
}
[main.cs]
When the user clicks on the [ADD] button on the main form in the playlist pane, the addplaylist [addplaylist.cs] form opens up.
Lets see a sreenshot of the addplaylist form:
The [addplaylist] form asks the user to fill in the name and description of the playlist to be created, the user also has to provide a thumbnail that represents the playlist. After filling these information, the user has to click on [ADD PLAYLIST] button which in turn executes the playlistadd() function which does the actual task of creating the playlist. Creating a playlist involves three basic steps :
private void playlistadd()
{
int thumbnailwidth = 150;
int thumbnailheight = 150;
try
{
#region add records in the playlist table for the new playlist
UInt32 newaddedplaylistid = user.addplaylist(nametxt.Text, new FileInfo(thumbnametxt.Text).Name, descriptiontxt.Text);
if (newaddedplaylistid == 0) throw new Exception();
#endregion
try
{
#region creating folders for the playlist on the remote server
//creating playlist folder
string createpath = user.ftpurl + "user_" + user.userid + "/playlist_" + newaddedplaylistid;
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(createpath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.KeepAlive = true;
request.Method = WebRequestMethods.Ftp.MakeDirectory;
request.GetResponse();
//creating audios folder in the playlist folder
createpath = user.ftpurl + "user_" + user.userid + "/playlist_" + newaddedplaylistid + "/audios";
request = (FtpWebRequest)FtpWebRequest.Create(createpath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.KeepAlive = true;
request.Method = WebRequestMethods.Ftp.MakeDirectory;
request.GetResponse();
#endregion
#region uploading thumbnail of the playlist to the remote server
try
{
int ChunkSize = 4096, NumRetries = 0, MaxRetries = 20;
byte[] Buffer = new byte[ChunkSize];
string onlythumbname = new FileInfo(thumbnametxt.Text).Name;
Image thumbimg = Image.FromFile(thumbnametxt.Text).GetThumbnailImage(thumbnailwidth, thumbnailheight, thumbnailcreationfailedcallback, IntPtr.Zero);
MemoryStream thumbimgstream = new MemoryStream();
thumbimg.Save(thumbimgstream, System.Drawing.Imaging.ImageFormat.Jpeg);
thumbimgstream.Position = 0;
string UploadPath = user.ftpurl + "user_" + user.userid + "/playlist_" + newaddedplaylistid + "/" + onlythumbname;
request = (FtpWebRequest)WebRequest.Create(UploadPath);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.KeepAlive = true;
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
using (Stream requestStream = request.GetRequestStream())
{
using (thumbimgstream)
{
int BytesRead = thumbimgstream.Read(Buffer, 0, ChunkSize); // read the first chunk in the buffer
// send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
while (BytesRead > 0)
{
try
{
requestStream.Write(Buffer, 0, BytesRead);
}
catch
{
if (NumRetries++ < MaxRetries)
{
// rewind the imagememeorystream and keep trying
thumbimgstream.Position -= BytesRead;
}
else
{
throw new Exception();
}
}
BytesRead = thumbimgstream.Read(Buffer, 0, ChunkSize); // read the next chunk (if it exists) into the buffer. the while loop will terminate if there is nothing left to read
}
}
}
}
catch (Exception)
{
//thumbnail could not be uploaded so remote folders have to be deleted
#region deleting folder that were created for the playlist
string deletepath = user.ftpurl + "user_" + user.userid + "/playlist_" + newaddedplaylistid;
new DeleteFTPDirectory().DeleteDirectoryHierarcy(deletepath);
#endregion
throw new Exception();
}
#endregion
}
catch (Exception)
{
#region deleting playlist record from the database
//If folder could not be created or thumbnail could not be created remove the playlist record from the playlist table in the database
user.deleteplaylist(newaddedplaylistid);
throw new Exception();
#endregion
}
}
catch (Exception)
{
MessageBox.Show("The playlist could not be added");
return;
}
MessageBox.Show("The playlist has been added");
//this.Close();
}
[addplaylist.cs]
When the user clicks on the [EDIT] button on the main form in the playlist pane, the editplaylist [editplaylist.cs] form opens up.
Lets see a sreenshot of the editplaylist form:
The [editplaylist] form shows the information of the playlist that can be edited by the user. After editing the information, the user has to click on [UPDATE PLAYLIST] button which in turn executes the playlistedit() function which does the actual task of updating the playlist. Editing the playlist involves two basic steps :
private void playlistedit()
{
bool thumbuploaded = true;
if (new FileInfo(thumbnametxt.Text).Name != playlisttoedit.thumb)
{
#region uploading thumbnail of the playlist to the remote server
try
{
int thumbnailwidth = 150;
int thumbnailheight = 150;
int ChunkSize = 4096, NumRetries = 0, MaxRetries = 20;
byte[] Buffer = new byte[ChunkSize];
string onlythumbname = new FileInfo(thumbnametxt.Text).Name;
Image thumbimg = Image.FromFile(thumbnametxt.Text).GetThumbnailImage(thumbnailwidth, thumbnailheight, thumbnailcreationfailedcallback, IntPtr.Zero);
MemoryStream thumbimgstream = new MemoryStream();
thumbimg.Save(thumbimgstream, System.Drawing.Imaging.ImageFormat.Jpeg);
thumbimgstream.Position = 0;
string UploadPath = user.ftpurl + "user_" + user.userid + "/playlist_" + playlisttoedit.id + "/" + onlythumbname;
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(UploadPath);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.KeepAlive = true;
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
using (Stream requestStream = request.GetRequestStream())
{
using (thumbimgstream)
{
int BytesRead = thumbimgstream.Read(Buffer, 0, ChunkSize); // read the first chunk in the buffer
// send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
while (BytesRead > 0)
{
try
{
requestStream.Write(Buffer, 0, BytesRead);
}
catch
{
if (NumRetries++ < MaxRetries)
{
// rewind the imagememeorystream and keep trying
thumbimgstream.Position -= BytesRead;
}
else
{
throw new Exception();
}
}
BytesRead = thumbimgstream.Read(Buffer, 0, ChunkSize); // read the next chunk (if it exists) into the buffer. the while loop will terminate if there is nothing left to read
}
}
}
}
catch (Exception)
{
//thumbnail could not be uploaded
thumbuploaded = false;
}
#endregion
}
if (thumbuploaded)
{
if (!(user.updateplaylist((UInt32)playlisttoedit.id, nametxt.Text, new FileInfo(thumbnametxt.Text).Name, descriptiontxt.Text)))
{
#region deleting the new thumbnail that was uploaded because the entry in the database could not be made
if (new FileInfo(thumbnametxt.Text).Name != playlisttoedit.thumb)
{
try
{
string deletepath = user.ftpurl + "user_" + user.userid + "/playlist_" + playlisttoedit.id + "/" + new FileInfo(thumbnametxt.Text).Name;
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(deletepath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.Method = WebRequestMethods.Ftp.DeleteFile;
request.KeepAlive = true;
request.GetResponse();
}
catch (Exception)
{
//do nothing if thumb could not be deleted, later on it'll be done via garbage collector
}
}
#endregion
MessageBox.Show("The playlist could not be updated");
}
else
{
#region deleting the old playlist thumb from the remote server and the local temp folder
if (new FileInfo(thumbnametxt.Text).Name != playlisttoedit.thumb)
{
try
{
string deletepath = user.ftpurl + "user_" + user.userid + "/playlist_" + playlisttoedit.id + "/" + playlisttoedit.thumb;
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(deletepath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.Method = WebRequestMethods.Ftp.DeleteFile;
request.KeepAlive = true;
request.GetResponse();
//deleting the old thumb from the local temp folder
deletepath = Application.StartupPath + "\\temp\\playlist_" + playlisttoedit.id + "\\" + playlisttoedit.thumb;
if (File.Exists(deletepath)) File.Delete(deletepath);
}
catch (Exception)
{
//do nothing if old thumb could not be deleted, later on it'll be done via garbage collector
}
}
#endregion
MessageBox.Show("The playlist has been updated");
}
}
else
{
MessageBox.Show("The playlist could not be updated");
}
}
[editplaylist.cs]
private void deleteplaylistbtn_Click(object sender, EventArgs e)
{
if (playlistview.SelectedItems.Count > 0)
{
if (DialogResult.OK == MessageBox.Show("Are you sure you want to delete the playlist?", "Delete Playlist", MessageBoxButtons.OKCancel))
{
UInt32 playlisttodeleteid = UInt32.Parse(playlistview.SelectedItems[0].ImageKey);
try
{
if (!(user.deleteplaylist(playlisttodeleteid))) throw new Exception();
Thread deleteplaylistthread = new Thread(deleteplaylistthreadfunction);
deleteplaylistthread.Start(playlisttodeleteid);
}
catch (Exception)
{
MessageBox.Show("Sorry the playlist could not be deleted");
return;
}
//MessageBox.Show("Playlist has been deleted.");
#region refresh the main form
playlistloadingindicator.Visible = true;
playlistloadingindicator.Active = true;
showplaylists();
playlistloadingindicator.Active = false;
playlistloadingindicator.Visible = false;
audioloadingindicator.Visible = true;
audioloadingindicator.Active = true;
if (playlistview.Items.Count > 0)
{
user.currentplaylistid = Int32.Parse(playlistview.Items[0].ImageKey);
showaudios(user.currentplaylistid);
}
else audiolistview.Items.Clear();
audioloadingindicator.Active = false;
audioloadingindicator.Visible = false;
#endregion
}
}
else MessageBox.Show("Please select the playlist to delete");
}
private void deleteplaylistthreadfunction(object data)
{
UInt32 playlisttodeleteid = (UInt32)data;
deleteplaylist frm = new deleteplaylist(playlisttodeleteid);
frm.ShowDialog();
}
[main.cs]
void folderdeletionthread()
{
#region deleting folder that were created for the playlist on the remote server and the local temp folder
try
{
//deleting on the remote server
string deletepath = user.ftpurl + "user_" + user.userid + "/playlist_" + playlisttodeleteid;
new DeleteFTPDirectory().DeleteDirectoryHierarcy(deletepath);
//deleting on the local temp folder
deletepath = Application.StartupPath + "\\temp\\playlist_" + playlisttodeleteid;
if (Directory.Exists(deletepath)) Directory.Delete(deletepath, true);
}
catch (Exception)
{
//In case the remote folders could not be deleted I'll do nothing they will later be garbage collected
}
#endregion
}
[deleteplaylist.cs]
When the user clicks on the [ADD] button on the main form in the audios pane, the upload [upload.cs] form opens up.
The [upload] form asks the user to fill in the title and description of the audio to be uploaded. After filling these information, the user has to click on [UPLOAD AUDIO] button which in turn executes the fileuploadbackgroundworker_DoWork() function (the background worker executes asynchronously) which does the actual task of adding/uploading the audio. Uploading a audio involves these basic steps :
private void uploadfilesbtn_Click(object sender, EventArgs e)
{
if (filename != string.Empty && titletxt.Text != string.Empty && descriptiontxt.Text != string.Empty)
{
uploadfilebtn.Enabled = false;
uploadfilebtn.Text = "PLEASE WAIT";
addcoverart(filename, "poster.jpg");
fileuploadbackgroundworker.RunWorkerAsync(filename);
}
else MessageBox.Show("Please fill in all the fields");
}
private void fileuploadbackgroundworker_DoWork(object sender, DoWorkEventArgs e)
{
string UploadPath;
FtpWebRequest request;
int ChunkSize = 102400, NumRetries = 0, MaxRetries = 3;
byte[] Buffer = new byte[ChunkSize];
//for updating status to the ui thread
decimal totalbytes = new FileInfo(filename).Length;
decimal totalbytesuploaded = 0;
//end
#region upload to remote, make database records,report progress to ui thread
string onlyfilename = new FileInfo(filename).Name;
bool fileuploadedremotely = true;
try
{
if (fileuploadedremotely)
{
#region upload audio to remote server
UploadPath = user.ftpurl + "user_" + user.userid + "/playlist_" + user.currentplaylistid + "/audios/" + onlyfilename;
request = (FtpWebRequest)WebRequest.Create(UploadPath);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
using (Stream requestStream = request.GetRequestStream())
{
using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read))
{
int BytesRead = fs.Read(Buffer, 0, ChunkSize); // read the first chunk in the buffer
// send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
while (BytesRead > 0)
{
try
{
requestStream.Write(Buffer, 0, BytesRead);
totalbytesuploaded += BytesRead;
#region report progress to the ui thread
int percentage = (int)(((float)totalbytesuploaded * 100) / ((float)totalbytes));
string statustext = string.Format("{0:0.00} Mb/{1:0.00} Mb", (float)(totalbytesuploaded / (1024 * 1024)), (float)(totalbytes / (1024 * 1024)));
fileuploadbackgroundworker.ReportProgress(percentage, statustext);
#endregion
}
catch
{
if (NumRetries++ < MaxRetries)
{
// rewind the filestream and keep trying
fs.Position -= BytesRead;
}
else
{
//MessageBox.Show("Could not upload " + onlyfilename + " after several attempts. Please check your network connectivity. This file will be skipped.");
fileuploadedremotely = false;
break;
}
}
BytesRead = fs.Read(Buffer, 0, ChunkSize); // read the next chunk (if it exists) into the buffer. the while loop will terminate if there is nothing left to read
}
}
}
#endregion
}
if (fileuploadedremotely)
{
#region add record to the database
if (!user.insertaudio(onlyfilename, titletxt.Text, descriptiontxt.Text, user.currentplaylistid)) fileuploadedremotely = false;
#endregion
}
}
catch
{
fileuploadedremotely = false;
}
if (!fileuploadedremotely)
{
user.deleteaudio(onlyfilename, user.currentplaylistid);
MessageBox.Show("The file could not be uploaded. The reason could be slow or no internet connectivity");
filename = string.Empty;
//code should be written here to delete the audio that was uploaded but some database error occured but that is a rare chance, so ignoring the code piece here
}
else
{
#region report progress to the ui thread
int percentage = 100;
string statustext = string.Format("{0:0.00} Mb/{1:0.00} Mb", (float)(totalbytes / (1024 * 1024)), (float)(totalbytes / (1024 * 1024)));
fileuploadbackgroundworker.ReportProgress(percentage, statustext);
#endregion
MessageBox.Show("The file has been uploaded");
filename = string.Empty;
}
#endregion
}
private void fileuploadbackgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressbar.Value = e.ProgressPercentage;
try
{
numofbytesuploadedstatuslbl.Text = e.UserState.ToString();
}
catch (Exception)
{
//do nothing
}
}
private void fileuploadbackgroundworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
uploadfilebtn.Enabled = true;
uploadfilebtn.Text = "UPLOAD AUDIO";
progressbar.Value = 0;
numofbytesuploadedstatuslbl.Text = "Operation Completed";
filenametxt.Text = "";
titletxt.Text = "";
descriptiontxt.Text = "";
filename = string.Empty;
}
[upload.cs]
When the user clicks on the [EDIT] button on the main form in the audios pane, the update audio [editaudio.cs] form opens up.
The Update Audio [editaudio.cs] form shows the information of the audio that can be edited by the user. After editing the information, the user has to click on [UPDATE AUDIO] button which in turn executes the audioedit() function which does the actual task of updating the information of the audio. Editing the audio involves one basic step :
private void editaudiobtn_Click(object sender, EventArgs e)
{
editaudiobtn.Enabled = false;
titletxt.Enabled = false;
descriptiontxt.Enabled = false;
editaudiobtn.Text = "Please Wait...";
Application.DoEvents();
if (string.IsNullOrEmpty(nametxt.Text) || string.IsNullOrEmpty(titletxt.Text) || string.IsNullOrEmpty(descriptiontxt.Text))
{
MessageBox.Show("Please fill in all the fields");
}
else
{
string[] fields = {titletxt.Text,descriptiontxt.Text};
editaudiothread = new Thread(audioedit);
editaudiothread.Start(fields);
while (editaudiothread.IsAlive) Application.DoEvents();
}
this.Close();
}
private void audioedit(object fields)
{
string[] title_decription = (string[])fields;
if (user.updateaudio(audio.id, title_decription[0], title_decription[1])) MessageBox.Show("The audio has been updated");
else MessageBox.Show("The audio could not updated due to a title mismatch or some internal errors. Try changing the title or try again later");
}
[editaudio.cs]
When the user clicks on the [DELETE] button on the main form in the audios pane, the DELETE AUDIOS [delete.cs] form opens up.
private void deleteaudiobtn_Click(object sender, EventArgs e)
{
Dictionary<string, string> audios = new Dictionary<string, string>();
string[] filenames = new string[audiolistview.SelectedItems.Count];
for (int i=0;i<audiolistview.SelectedItems.Count;i++)
{
audios.Add(audiolistview.SelectedItems[i].ImageKey, audiolistview.SelectedItems[i].Text);
}
if (audios.Count > 0)
{
delete frm = new delete(audios);
frm.ShowDialog();
}
else MessageBox.Show("Please select some audios to delete");
//refresh the audio view list box
audioloadingindicator.Visible = true;
audioloadingindicator.Active = true;
showaudios(user.currentplaylistid);
audioloadingindicator.Active = false;
audioloadingindicator.Visible = false;
//end refresh
}
[main.cs]
private void deleteaudiosbackgroundworker_DoWork(object sender, DoWorkEventArgs e)
{
long totalfiles = audios.Keys.Count;
long totalfilesdeleted = 0;
long totalfilesskipped = 0;
//selecting each audio one by one and deleting it from database,local temp and remote server
foreach (KeyValuePair<string, string> audio in audios)
{
bool filedeleted = false;
string audiofilename=user.getaudiofilename(ulong.Parse(audio.Key));
#region deleting audio record from database
if (user.deleteaudio(ulong.Parse(audio.Key))) filedeleted = true;
#endregion
if (filedeleted == true)
{
#region deleting audio from the local temp folder
try
{
string pathtoaudio = Application.StartupPath + "\\temp\\playlist_" + user.currentplaylistid + "\\audios\\" + audiofilename;
if (File.Exists(pathtoaudio)) File.Delete(pathtoaudio);
}
catch
{
//do nothing. Later on we can do garbage collection in the local temp folder
}
#endregion
}
if (filedeleted == true)
{
#region deleting audio from the remote server
try
{
#region deleting audio from the remote server
string deletepath = user.ftpurl + "user_" + user.userid + "/playlist_" + user.currentplaylistid + "/audios/" + audiofilename;
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(deletepath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.KeepAlive = true;
request.Method = WebRequestMethods.Ftp.DeleteFile;
request.GetResponse();
#endregion
}
catch
{
//do nothing. Later on we could do garbage collection at the remote server
}
#endregion
}
if (filedeleted == true)
{
totalfilesdeleted++;
}
else
{
totalfilesskipped++;
}
long[] userstate=new long[2];
userstate[0]=totalfilesdeleted;
userstate[1]=totalfiles;
int percentage = (int)((float)(totalfilesdeleted * 100) / (float)(totalfiles - totalfilesskipped));
deleteaudiosbackgroundworker.ReportProgress(percentage, userstate);
}
if (totalfilesskipped > 0) MessageBox.Show("Some of the files could not be deleted");
}
private void deleteaudiosbackgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string labeltxt = ((long[])e.UserState)[0].ToString() + "/" + ((long[])e.UserState)[1].ToString();
deletestatuslbl.Text = labeltxt;
deleteprogressbar.Value = e.ProgressPercentage;
}
private void deleteaudiosbackgroundworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
deletestatuslbl.Text = "Operation Completed";
MessageBox.Show("The delete operation has completed");
this.Close();
}
[delete.cs]
When the user clicks on [MANAGE USERS] button, the Manage Users [manageuser.cs] form opens up. This form contains three tabs named: Create User, Edit User and Delete User.
Lets see a screenshot of the Create User tab in the Manage User form :
public static bool createuser(string name,string username,string password,string description)
{
bool created = true;
UInt32 userid=0;//Used 0 here creating a new user will never result in a 0 because I create the superuser with the id=0 myself and don't let it deleted. So...
try
{
#region Inserting record in the database
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "INSERT INTO user (name,username,password,description) VALUES(@name,@username,@password,@description)";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@name", name);
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
cmd.Parameters.AddWithValue("@description", description);
if (cmd.ExecuteNonQuery() < 0) throw new Exception();
conn.Close();
#endregion
#region Getting the id of newly inserted user
if (conn.State == ConnectionState.Closed) conn.Open();
query = "SELECT id FROM user WHERE username=@username";
cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@username", username);
MySqlDataReader rdr = cmd.ExecuteReader();
if (rdr.Read()) userid = UInt32.Parse(rdr["id"].ToString());
else throw new Exception();
conn.Close();
#endregion
#region Creating folder for user on the remote site
string createpath = user.ftpurl + "user_" + userid;
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(createpath);
request.Credentials = new NetworkCredential(user.ftpusername, user.ftppassword);
request.KeepAlive = true;
request.Method = WebRequestMethods.Ftp.MakeDirectory;
request.GetResponse();
#endregion
}
catch
{
created = false;
#region Deleting record from the database
if (userid != 0)
{
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "DELETE FROM user WHERE id=@id";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@id", userid);
cmd.ExecuteNonQuery();
conn.Close();
}
#endregion
}
return created;
}
[user.cs]
When the user clicks on [Edit User] tab, the Edit User tab becomes visible.
public static bool edituser(UInt32 id,string name, string username, string password, string description)
{
bool edited = true;
try
{
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "UPDATE user SET name=@name,username=@username,password=@password,description=@description WHERE id=@id";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@name", name);
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
cmd.Parameters.AddWithValue("@description", description);
if (cmd.ExecuteNonQuery() < 0) throw new Exception();
conn.Close();
}
catch
{
edited = false;
}
return edited;
}
[user.cs]
When the user clicks on [Delete User] tab, the Delete User tab becomes visible.
public static bool deleteuser(UInt32 id)
{
bool deleted = true;
try
{
#region Deleting record from the database
if (conn.State == ConnectionState.Closed) conn.Open();
string query = "DELETE FROM user WHERE id=@id";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@id", id);
if (cmd.ExecuteNonQuery() < 0) throw new Exception();
conn.Close();
#endregion
#region Delete folder of the user on the remote site
string deletepath = user.ftpurl + "user_" + id;
new DeleteFTPDirectory().DeleteDirectoryHierarcy(deletepath);
#endregion
}
catch
{
deleted = false;
}
return deleted;
}
[user.cs]