Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Audio-Gallery-Suite (A complete audio gallery solution made with HTML5/CSS3/jQuery-JS/PHP/C#)

4.99/5 (82 votes)
30 Apr 2013MIT31 min read 294.6K   6.3K  
Audio-Gallery-Suite is a complete audio gallery solution made with HTML5/CSS3/Jquery-JS/PHP-Ajax/C# that includes a web audio gallery and a software for managing the web audio gallery.

Watch this video demonstration before diving in : Lets Watch!!! 

Download the most updated version of Audio Gallery Suite from GITHUB. (Star this repo on github) 

Updated (30-April-2013) - Audio-Gallery-Suite has been updated and now has a web based management interface for the audio gallery named GALLARDMIN. Gallardmin provides with sophisticated easy to use full featured management interface. It uses the power of latest xhr2, html5 and javascript techniques.  

Updated (02-July-2012) - Audio-Gallery-Suite has been updated and is now compatible with IE9

Index  

Description

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.

Technology

  • HTML5
  • CSS3
  • JavaScript/jQuery
  • AJAX
  • PHP
  • MySQL
  • .NET/C#

Demonstration

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/Contribute

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 GitHub using the same link given below.
https://github.com/robinrizvi/Audio-Gallery-Suite

Features

Web Gallery

  • Dynamic loading of playlists.
  • Dynamic loading of audios belonging to the playlist
  • Dynamic audio search
  • Easy to use audio player
  • Options like play, pause, next, previous, volume control, and seeking are available
  • Audio player can be operated using keyboard keys
  • A rotating disc to indicate the playing status of a audio
  • Audio sharing to popular social networks
  • Currently selected audio can be downloaded
  • Direct link to audio file can be seen and copied
  • Fluid Layout, the web gallery adjusts itself when browser is resized

Gallery Software

  • Playlists can be added, edited and deleted
  • Audios can be added to any of the added playlists
  • Audios can be edited and deleted
  • Settings such as changing username and password can be configured
  • Other users can be added, edited and deleted (only through the superuser account)
  • Responsive/non-blocking user-interface
  • Progress of uploading the audio is displayed
  • Each audio to be uploaded is tagged with a poster(also called album-art) before uploading 

Setup (using Gallardmin - web based gallery management software)

  • Download this zip: https://github.com/robinrizvi/Audio-Gallery-Suite/archive/master.zip 
  • Execute these sql scripts (present in Resources/SQL Script) in your mysql server: 1)audiogallery_db.sql 2) create_user_[optional].sql
  • Configure config.php (present in Gallery/php) if the there are any changes to the database or the database user other than the default ones assumed and made by the execution of the above mentioned sql scripts. 
  • Copy/upload the contents of Gallery folder from the zip to your server.
  • Go to http://root_url_to_gallery_folder/management/audiogallery/login.html
  • Type superuser and superuser as username and password respectively. 
  • Add playlist and add audios under that playlist.
  • Go to your http://root_url_to_gallery_folder/index.html to view your gallery 

Setup (at remote server using windows based management software)

Note

  • The square brackets [] are just used to indicate significance, they should be ignored otherwise.
  • [root_url_of_web_gallery] refers to the folder or url on your webserver where the files of the web gallery will reside. Example : In my demonstration (see Demonstration section above), it is  http://projects.robinrizvi.info/audio-gallery-suite/ (http://www.yourdomain.com/foldername/). 

Initial Steps

  • Download the complete Audio-Gallery-Suite from GitHub (See the Download/Contribute section above). Extract the contents of the downloaded zip file.
  • Now you must have a folder [Audio-Gallery-Suite] containing the audio-gallery-suite in your disk. 
  • Open the [Audio-Gallery-Suite\Gallery] folder on your disk and create a folder [audiogallery] in it.  
  • Now open the [Audio-Gallery-Suite\Gallery\audiogallery] folder and create a folder [user_0] in it. It should be like this [Audio-Gallery-Suite\Gallery\audiogallery\user_0] on your disk. 

Upload Web Gallery :  

  • Upload all the files from the [Audio-Gallery-Suite\Gallery] folder on your disk to your server/host in the [root_url_of_web_gallery] (See "Note" above). 

Configure FTP : 

  • Create a FTP account at your server/host which points to the [root_url_of_web_gallery/audiogallery] directory on the server. Remember we will use (FTP servername, FTP username, FTP password), so make a note of them.

Create Database :  

  • Open the [audiogallery_db.sql] present in [Audio-Gallery-Suite\Resources\SQL Script] folder from your disk. (You can open the sql file in any text editor like notepad etc). 
  • If you want to change the database name from the default name [audiogallery] to some other name, you can do it by changing the line [CREATE DATABASE IF NOT EXISTS audiogallery;USE audiogallery;] to [CREATE DATABASE IF NOT EXISTS some_other_database_name;USE some_other_database_name;]
  • If you have already created a database with some other name, delete the line [CREATE DATABASE IF NOT EXISTS audiogallery;] from the sql script and edit the line [USE audiogallery] like this [USE your_other_db_name;].
  • If you want to change the SUPERUSER’s username and password you can change that from the [audiogallery_db.sql] like this: [INSERT INTO `user` (`id`,`username`,`password`,`name`,`description`) VALUES
    (0,'your_superuser_username','your_superuser_password','Super User','Superuser that creates and manages other users and can also create also create and manage his own playlists and audios');]. 
  • Change the ftp settings in the [audiogallery_db.sql] file like this: [INSERT INTO `settings` (`ftpurl`,`ftpusername`,`ftppassword`) VALUES ('ftp://your_ftp_server/','ftp_username','ftp_password');]. Remember these three values are the values that we got when we created the FTP account at the server (See "Configure FTP" section above). 
  • Make sure the superuser configuration and ftp configuration is properly set before saving and executing the [audiogallery_db.sql] file.
  • Save the [audiogallery_db.sql] file.
  • Execute the [audiogallery_db.sql] after doing the above changes (It, by default, creates a database named audiogallery). You can execute the sql script by doing an "IMPORT" using PHPMYADMIN of your server (Screenshot) or you can download MySQL Workbench (Download Link) and connect to your remote database server and then import the sql script and execute it.
  • Remember we need to make a note of the database name, the username and password of the user assigned to the database (i.e the user authorized to do operations (run queries) on the database we created) 

Configure PHP :  

  • Open the [config.php] file from the [Audio-Gallery-Suite\Gallery\php] folder on your disk. You can open this file in any text editor. 
  • Configure the database settings in the [config.php] like this: [$mysqli = new mysqli("localhost", "DB_USERNAME", "DB_PASSWORD", "DB_NAME"); ]. Remember the [localhost] need not to be changed and the rest of the three values are the values that we got when we created the database at the server (See "Create Database" section above).  
  • Configure the [$root] variable in the [config.php] file like this: [$root= “http://root_url_of_web_gallery/”;]. What is [root_url_of_web_gallery] ? (See "Note" section above). Example: [$host=”http://projects.robinrizvi.info/audio-gallery-suite/”] is used in the my demonstration (See Demonstration section above). Remember don't forget to put the trailing forward slash at the end. 
  • Save the [config.php] file after doing the above changes.
  • Upload the [config.php] file from the [Audio-Gallery-Suite/Gallery/php] folder on your disk to your server/host in the [root_url_of_web_gallery/php] folder (Replace the old [config.php] file).

Configure Web Gallery Software :

  • Open the [AUDIO GALLERY.sln] file from the [Audio-Gallery-Suite\Software] folder on your disk in Visual Studio.
  • Open the Resources (from Solution-Explorer->Properties->Resources) from the audiogallery project.
  • Edit the [mysqlconnectionstring] setting from the Resources to properly configure the Mysql database connection settings like this: [server=DB_SERVER_HOST;User Id=DB_USERNAME;Persist Security Info=True;database=DB_NAME;password=DB_PASSWORD]. Remember DB_SERVER will refer to your server (i.e where your database server is running) like  server=your_host_domain.com and the rest three (DB_USERNAME, DB_NAME, DB_PASSWORD) will be the ones we noted down when we created the database (See "Create Database" section above). 
  • Rebuild the whole solution.

Add some playlists and audios :

  • Now run the software (from the build exe or from visual studio or from the audiogallery_setup.exe)
  • Log in using the SUPERUSER’s username and password (default is [superuser] and [superuser]).
  • Here you can create some playlists and add audios to them, so that the web gallery shows some audios.
  • Wipe the sweat and be happy to see it all working !

Setup (at local server/local host using windows based management software)

Note :  

  • The square brackets [] are just used to indicate significance, they should be ignored otherwise. 
  • [root_url_of_web_gallery] refers to the url of your testing local server where the files of the web gallery will reside. Example : http://localhost/gallery/, in my local setup [gallery] is the folder I created in my [www] folder of my [apache webserver] (and [http://localhost] is the url (loopback) of my machine which points to the [www] folder).
  • [root_folder_of_web_gallery] refers to the folder in your testing local server where the files of the web gallery will reside. Example : In my local setup http://localhost would point to [C:\Program Files (x86)\EasyPHP-5.3.6.1\www] folder and then I created a folder within www folder named gallery, so my [root_folder_of_web_gallery] is  [C:\Program Files (x86)\EasyPHP-5.3.6.1\www\gallery\] and my [root_url_of_web_gallery] is  [http://localhost/gallery/]  

Initial Steps :   

  • Download the complete Audio-Gallery-Suite from GitHub (See the Download/Contribute section above). Extract the contents of the downloaded zip file.
  • Now you must have a folder [Audio-Gallery-Suite] containing the audio-gallery-suite in your disk. 
  • Open the [Audio-Gallery-Suite\Gallery] folder on your disk and create a folder [audiogallery] in it. 
  • Now open the [Audio-Gallery-Suite\Gallery\audiogallery] folder and create a folder [user_0] in it. It should be like this [Audio-Gallery-Suite\Gallery\audiogallery\user_0] on your disk.
  • You must have WAMP (Windows+Apache+MySQL+PHP) stack setup in your local computer. If you don't have it yet, you can download it from EasyPHP Download Link. Remember you can also choose the LAMP stack but for running the .net software it is recommend that you have WAMP.
  • You must have a local FTP server on your computer. You can use IIS or Xlight ftp server (See Xlight usage instructions). 

Upload Web Gallery :  

  • Copy all the files from the [Audio-Gallery-Suite\Gallery] folder on your disk to your local web server's folder in the [root_folder_of_web_gallery] folder (See "Note" above). Example -  I copied all files to this folder [C:\Program Files (x86)\EasyPHP-5.3.6.1\www\gallery\]  

Configure FTP : 

  • Add a FTP site at your local computer using (Xlight - See Xlight usage instructions or IIS) which points to the [root_folder_of_web_gallery\audiogallery] directory on the server. Remember we will use (FTP servername, FTP username, FTP password), so make a note of them. Example - My FTP servername was localhost, FTP username was robin and FTP password was robinr and my ftp site was pointed to [C:\Program Files (x86)\EasyPHP-5.3.6.1\www\gallery\audiogallery] folder 

Create Database :  

  • Open the [audiogallery_db.sql] present in [Audio-Gallery-Suite\Resources\SQL Script] folder from your disk. (You can open the sql file in any text editor like notepad etc). 
  • If you want to change the database name from the default name [audiogallery] to some other name, you can do it by changing the line [CREATE DATABASE IF NOT EXISTS audiogallery;USE audiogallery;] to [CREATE DATABASE IF NOT EXISTS some_other_database_name;USE some_other_database_name;]
  • If you have already created a database with some other name, delete the line [CREATE DATABASE IF NOT EXISTS audiogallery;] from the sql script and edit the line [USE audiogallery] like this [USE your_other_db_name;].
  • If you want to change the SUPERUSER’s username and password you can change that from the [audiogallery_db.sql] like this: [INSERT INTO `user` (`id`,`username`,`password`,`name`,`description`) VALUES 
    (0,'your_superuser_username','your_superuser_password','Super User','Superuser that creates and manages other users and can also create also create and manage his own playlists and audios');]. 
  • Change the ftp settings in the [audiogallery_db.sql] file like this: [INSERT INTO `settings` (`ftpurl`,`ftpusername`,`ftppassword`) VALUES ('ftp://your_local_ftp_server_url/','ftp_username','ftp_password');]. Remember these three values are the values that we got when we created the FTP account at the server (See "Configure FTP" section above). Example - 'ftp://localhost/', 'robin', 'robinr' were my three values. 
  • Make sure the superuser configuration and ftp configuration is properly set before saving and executing the [audiogallery_db.sql] file.
  • Save the [audiogallery_db.sql] file.
  • Execute the [audiogallery_db.sql] after doing the above changes (It, by default, creates a database named audiogallery). You can execute the sql script by doing an "IMPORT" using PHPMYADMIN of your local server (Screenshot) or you can download MySQL Workbench (Download Link) and connect to your local database server and then import the sql script and execute it. 
  • Remember we need to make a note of the database name, the username and password of the user assigned to the database (i.e the user authorized to do operations (run queries) on the database we created). If you are experiencing any problem in figuring the username-password of the user for our database we just created you can change/configure and execute the [create_user_[optional].sql] sql script found in [Audio-Gallery-Suite\Resources\SQL Script] folder.  

Configure PHP :  

  • Open the [config.php] file from the [root_folder_of_web_gallery\php] folder. You can open this file in any text editor. Example - The config.php file in my local setup resided in [C:\Program Files (x86)\EasyPHP-5.3.6.1\www\gallery\php\config.php]  
  • Configure the database settings in the [config.php] like this: [$mysqli = new mysqli("localhost", "DB_USERNAME", "DB_PASSWORD", "DB_NAME"); ]. Remember the [localhost] need not to be changed and the rest of the three values are the values that we got when we created the database at the server (See "Create Database" section above). Example - [$mysqli = new mysqli("localhost", "audgaldbusr", "audgaldbpwd", "audiogallery");] was used in my local setup.  
  • Configure the [$root] variable in the [config.php] file like this: [$root= “http://root_url_of_web_gallery/”;]. What is [root_url_of_web_gallery] ? (See "Note" section above). Example: [$host= "http://localhost/gallery/";] was used in the my local setup. Remember don't forget to put the trailing forward slash at the end.  
  • Save the [config.php] file after doing the above changes. 

Configure Web Gallery Software :

  • Open the [AUDIO GALLERY.sln] file from the [Audio-Gallery-Suite\Software] folder on your disk in Visual Studio.
  • Open the Resources (from Solution-Explorer->Properties->Resources) from the audiogallery project.
  • Edit the [mysqlconnectionstring] setting from the Resources to properly configure the Mysql database connection settings like this: [server=DB_SERVER_HOST;User Id=DB_USERNAME;Persist Security Info=True;database=DB_NAME;password=DB_PASSWORD]. Example - [server=localhost;User Id=audgaldbusr;Persist Security Info=True;database=audiogallery;password=audgaldbpwd], this is the setting I used in my local setup. Remember DB_SERVER will refer to your local host/server (i.e where your database server is running) and the rest three (DB_USERNAME, DB_NAME, DB_PASSWORD) will be the ones we noted down when we created the database (See "Create Database" section above). 
  • Rebuild the whole solution

Add some playlists and audios : 

  • Now run the software (from the build exe or from visual studio or from the audiogallery_setup.exe)
  • Log in using the SUPERUSER’s username and password (default is [superuser] and [superuser]). 
  • Here you can create some playlists and add audios to them, so that the web gallery shows some audios. 
  • Time to be happy again !  

Insight into the code

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.

Note: The important parts of the code are marked in BOLD ITALICS. 

How it works

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:

  1. The [audiogallery] directory on the server/host where the audios will be uploaded.
  2. The database which contains the details about the audios, playlists, users, and general settings used by the suite.
  3. The web audio gallery that presents the interface to the web users to listen to audios on the website.
  4. The audio gallery software that is used by authorized users to create playlists and upload audios.
333670/suite_comp_bonding.jpg

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!).

  • Log into the audio gallery software using the superuser username/password. The superuser account is the default account that is present initially.
  • Click on the [Manage Users] button.
  • Create a user account [user_test] (Note: Only superuser can create users)
  • After you create the user [user_test], a record is entered into [user] table and an auto [id] is created for that user (say, user_test has an id=2).
  • And the [audiogallery] directory on the server/host now looks like this: {Root}/audiogallery/user_2
  • Now log into the gallery software with the [user_test] account.
  • Then create a playlist [playlist_test] by clicking the [Add] button in the Playlist pane.
  • As a result of adding the playlist, a playlist entry is added to the [playlist] table in the database (database is named audiogallery by default) and an auto [id] is generated for the playlist (say, playlist_test has id=1) and also note that the record for [playlist_test] in the [playlist] table specifies the user [user_test] as its user i.e [user_id=2] for [playlist_test].
  • And the [audiogallery] directory on the server/host looks like this: {Root}/audiogallery/user_2/playlist_1/audios
  • Now click on this [playlist_test] in the software to make it the current playlist.
  • Next add some audios to [playlist_test] using the [Add] button in the Audios pane.
  • As you add the audios, records are added to the [audio] table in the database. Each record of the [audio] table specifies the playlist to which the audio belongs by specifying the correct [playlist_id] in the record, which in our case for the newly added audios will be [playlist_id=1].
  • And the added audios will be uploaded to this directory like this: {Root}/audiogallery/user_2/playlist_1/audios/audio_test.mp3
  • Now when you open the web gallery, after the page/DOM has been loaded, the playlists are loaded into the dropdown by querying the [playlist] table in the database.
  • Now when you select your playlist [playlist_test] from the [Select Playlist] dropdown in the web gallery, the audios belonging to [playlist_test] are loaded by querying the [audio] table in the database.
  • After selecting the [playlist_test] playlist, you will see [audio_test] audio/mp3 file (and any other files which you might have uploaded under the [playlist_test] playlist) in the audiolist in the web gallery.
  • Now just click on the [audio_test] mp3 in the web gallery and enjoy some melodious music!

Check out the the video below to clearly understand the scenario discussed above.

Watch on Youtube.

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).

  1. Directory Structure 
  2. Database Structure 
  3. Web Gallery 
  4. Gallery Software  

1. Directory Structure

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/

333670/5qxmc_New-Sheet.jpg

2. Database Structure

333670/audiogallery_db_dig.jpg

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.

3. Web Gallery

HTML Skeleton

333670/cu29_New-Sheet.jpg

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]

333670/scr1.jpg

The original HTML code and structuring of the [index.html] can be seen below.

HTML Code

XML
<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.

Loading Playlists 

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.

Let's see a screenshot after the playlists have been loaded:

333670/playlist_loading.jpg

Now let us see the code from the different files that make it happen:

XML
<select name="playlistselect" id="playlistselect">
    <!--Content will be loaded via ajax php from the database-->
</select>

[index.html]

PHP
//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]

JavaScript
//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]

Loading Audios

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.

Let's see a screenshot after the audios have been loaded:

333670/audio_list.jpg

Now let us see the code from the different files that make it happen:

XML
 <div id="audiolist">
    <!--Content will be loaded via ajax php from the database.-->
</div>

[index.html]

JavaScript
//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]

>Initializing Playlist

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:

JavaScript
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]

>Playing audio/Initializing Jplayer

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.

Let's see a screenshot of the (custom) Jplayer interface on the web gallery:

333670/jplayer.jpg

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]

>Rotating Disc

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.

Let's see a screenshot of the rotating disc as seen in the web gallery:

333670/rotating_disc.jpg

Now let us see the code from the different files that make it happen:

XML
 <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]

JavaScript
$(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]

>Keyboard Support

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(){---}); )

Let's see a screenshot showing the keys that can be used to control the player:

333670/keyboard_support.jpg

Now let us see the code that makes it happen:

JavaScript
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]

>Downloading Audio

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:

333670/download.jpg

Now let us see the code from the different files that make it happen:

XML
<a id="downloadaudiotool" target="_blank" 
             class="button gray medium">DOWNLOAD</a> 

[index.html]

JavaScript
$(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

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.

Let's see a screenshot of the share button as seen in the web gallery:

333670/share.jpg

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]

4. Gallery Software

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.

Let Us see a screenshot giving the overview of the gallery software:

333670/gallery_software.jpg

Now let's start with the discussion of the basic functionalities of the gallery software and their implementation.

Login

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 examining 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).

Let's see a screenshot of the login form:

333670/sw1.jpg

Now let us see the code that makes it happen:

C#
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]

Show Playlists

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)

C#
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

C#
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]

Let's see a screenshot showing the playlistview (Listview) in the main form:

333670/playlistview.jpg

Show Audios

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):

C#
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

C#
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]

Let's see a screenshot showing the audiolistview (Listview) in the main form:

333670/audiolistview.jpg

Add Playlist

When the user clicks on the [ADD] button on the main form in the playlist pane, the addplaylist [addplaylist.cs] form opens up.

Let's see a sreenshot of the addplaylist form:

333670/sw5.jpg

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 :

  1. Add records in the [playlist] table in the database for the new playlist.
  2. Creating folders for the playlist on the remote server (See the Directory Structure section above).
  3. Uploading thumbnail of the playlist to the remote server.

Let's see the code on how these 3 basic steps are implemented:

C#
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]

Edit Playlist

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 screenshot of the editplaylist form:

333670/sw8.jpg

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 :

  1. Update records in the [playlist] table in the database for the playlist.
  2. Uploading thumbnail of the playlist to the remote server if the thumbnail is changed.

Let's see the code on how these basic steps are implemented:

C#
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)
                {
                    // read the first chunk in the buffer
                    int BytesRead = thumbimgstream.Read(Buffer, 0, ChunkSize);
                    // 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]

Delete Playlist

When the user clicks on the [DELETE] button on the main form in the playlist pane, the currently active playlist is selected for deletion. Deleting the playlist involves two basic steps:

  1. Delete records in the [playlist] table and the [audio] table in the database for the selected playlist.
  2. Delete folders that were created for that playlist at the remote server.

Let's see the code on how these steps are implemented:

C#
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]

C#
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]

>Add Audio

When the user clicks on the [ADD] button on the main form in the audios pane, the upload [upload.cs] form opens up.

Let's see a screenshot of the upload form:

333670/sw7.jpg

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 :

  1. Add records in the [audio] table in the database for the audio to be uploaded.
  2. Upload audio to the audios directory at the remote server (See the Directory Structure section above). The audios are uploaded by using the .NET's FTPWEBREQUEST class (MSDN).
  3. Report progress as the audio is being uploaded i.e., showing the percentage and bytes of audio that have been uploaded (See the screenshot above).

Let's see the code on how these basic steps are implemented:

C#
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]

Edit Audio

When the user clicks on the [EDIT] button on the main form in the audios pane, the update audio [editaudio.cs] form opens up.

Let's see a sreenshot of the Update Audio form:

333670/sw9.jpg

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:

  1. Update records in the [audio] table for the audio that is being updated.

Let's see the code on how it is implemented:

C#
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]

Delete Audio

When the user clicks on the [DELETE] button on the main form in the audios pane, the DELETE AUDIOS [delete.cs] form opens up.

Let's see a sreenshot of the DELETE AUDIOS form

Image 21

The DELETE AUDIOS [delete.cs] form shows the audios that the user has selected for deletion (Multiple audios can be deleted at once). When the user clicks on [DELETE AUDIOS] button, deleteaudiosbackgroundworker_DoWork() function (it is an asynchronous function) is executed, which does the actual task of deleting the audio. Deleting a audio involves these basic steps:

  1. Delete records of the audio from the [audio] table in the database.
  2. Delete audio from the audios directory at the remote server (See the Directory Structure section above).
  3. Report progress as the audios are being deleted i.e showing the percentage and number of audios that have been deleted.

Let's see the code on how these basic steps are implemented:

C#
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]

C#
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]

Create User

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.

Let's see a screenshot of the Create User tab in the Manage User form:

Image 22

When the user clicks on [CREATE USER] button (See the screenshot above) after filling all the information, the createuser() method in the user class [user.cs] is executed. Creating a user involves these basic steps :

  1. Add records in the [user] table in the database for the user to be created. 
  2. Create a directory for the user at the remote server in the form audiogallery/user_id (See the Directory Structure section above). 

Lets see the code on how these basic steps are implemented:

C#
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]

>Edit User

When the user clicks on [Edit User] tab, the Edit User tab becomes visible. 

Let's see a screenshot of the Edit User tab in the Manage User form:

Image 23

When the user clicks on [EDIT USER] button (See the screenshot above) after updating all/some of the information, the edituser() method in the user class [user.cs] is executed. The user.edituser() method just executes an UPDATE SQL query to update all the information for the particular user.

Let's see the code on how these basic steps are implemented:

C#
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]

Delete User

When the user clicks on [Delete User] tab, the Delete User tab becomes visible. 

Let's see a screenshot of the Delete User tab in the Manage User form:

Image 24

When the user clicks on [DELETE USER] button after selecting the user from the user list (dropdownlist)  (See the screenshot above), the deleteuser() method in the user class [user.cs] is executed. Deleting a user involves these basic steps :

  1. Delete records from the [user] table in the database for the user to be deleted. 
  2. Remove the directory that was created for that user at the remote server (See the Create User section above).  

Let's see the code on how these basic steps are implemented:

C#
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]

Screens

Web Gallery

333670/scr1.jpg

Gallery Software

333670/sw2.jpg

This project is in continuous development at Github (https://github.com/robinrizvi/Audio-Gallery-Suite) and your comments and suggestions and queries are most welcome for further improvement to the project.

I hope that the Audio-Gallery-Suite proves useful to anyone who wants to implement a complete audio gallery for their use.

License

This article, along with any associated source code and files, is licensed under The MIT License