MP3-CMS Introduction
Resources
Even in 2007 managing digital music libraries remains an enduring problem in a distributed environment. Most of us have pcs or pc appliances in just about every room in the house. Making sure all of these devices all have access to the family's digital music library seems as simple as creating a file server. However as the features included in these standalone players increase, so do application footprints. Each application needs to spend countless hours scanning the file server for music, updating it's database, and then there's the problem of synchronization or having to make sure that all these applications have the newest music in their databases. Ultimately it becomes quite a administrative task.
What is needed is a single interface, with common features that runs entirely from a web server. It needs to be intelligent. Specifically it should allow consumers to create playlists, rate tracks, comment on albums, generate smart playlists based on criteria such as star ratings, genres, newest tracks, and most importantly it should remember which tracks have been played and how often. Based on such analysis the system should recommend music and this system should be self-contained with as little administration as possible. If the users add or delete music from the server, the system should update itself appropriately.
Obviously the CMS is huge and has a degree of complexity that cannot be covered in one article so this article will focus on the core elements I used to satisfy my goals.
Using the Code
For Users
Installing the CMS for users is really simple: See the instructions here for installation.
For Developers
We're using subversion for source control and you can get the code (the current trunk or the entire source tree) from our source-forge repository. A complete set of instructions can be found here.
My Problem
So back in 2006 or so I was struggling with trying to keep all the music player software on all my computers synchronized and uniform. My Linux seats had Amarok and my Windows seats had a mix of WinAmp, iTunes, and Windows Media Player (argh..). After years of complaining about this hassle I started building an ASPNET based mp3 content management system. My needs were greater than my skill at the time so I strived to leverage re-usable components and recruit collaborators to help me out with this dream project.
I located an open source flash-based mp3 component from Jeroen Wijering and decided to use it in the application. The component uses an XSPF XML playlist format so all I needed to do was execute a database query and build a properly formed XSPF document to feed to it. To encapsulate the player's JavaScript and properties, a custom UserControl was created to wrap the player:
Mp3Player User Control
public partial class controls_Mp3Player : System.Web.UI.UserControl
{
private string _playlist = null;
private bool _shuffle = true;
private string _movie = "../player/mp3player.swf";
private string _scriptPath = "../player/swfobject.js";
private string _width = "500";
private string _height = "400";
private string _bgcolor = "#FFFFFF";
private string _lightcolor = "0x0C0C0C";
private string _forecolor = "0x000000";
private string _backcolor = "0xFFFFFF";
private string _displayheight = "200";
private bool _repeat = true;
private bool _displaythumbs = true;
private bool _overstretch = false;
private bool _autoscroll = false;
private string _callbackPath = "../player/SetPlayCount.aspx";
protected void Page_Load(object sender, EventArgs e)
{ }
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
writer.Write(GetHtml());
}
public string GetHtml()
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat("<script type=\"text/javascript\"
src=\"{0}\"></script>\n", _scriptPath);
builder.Append("<p id=\"player\"><a
href=\"http://www.macromedia.com/go/getflashplayer\">
Get the Flash Player</a> to see this player.</p>\n");
builder.Append("<script type=\"text/javascript\">");
builder.AppendFormat("var so = new SWFObject('{0}','mpl',
'{1}','{2}','7');\n", _movie, _width, _height);
builder.AppendFormat("so.addVariable(\"height\",\"{0}\");\n", _height);
builder.AppendFormat("so.addVariable(\"width\",\"{0}\");\n", _width);
builder.AppendFormat("so.addVariable(\"file\",\"{0}\");\n",
HttpUtility.UrlEncode(_playlist));
builder.AppendFormat("so.addVariable(\"repeat\",\"{0}\");\n",
_repeat.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"overstretch\",\"{0}\");\n",
_overstretch.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"displayheight\",\"{0}\");\n",
_displayheight);
builder.AppendFormat("so.addVariable(\"shuffle\",\"{0}\");\n",
_shuffle.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"thumbsinplaylist\",\"{0}\");\n",
_displaythumbs.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"overstretch\",\"{0}\");\n",
_overstretch.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"lightcolor\",\"{0}\");\n",
_lightcolor);
builder.AppendFormat("so.addVariable(\"backcolor\",\"{0}\");\n",
_backcolor);
builder.AppendFormat("so.addVariable(\"frontcolor\",\"{0}\");\n",
_forecolor);
builder.AppendFormat("so.addVariable(\"autoscroll\",\"{0}\");\n",
_autoscroll.ToString().ToLower());
builder.AppendFormat("so.addVariable(\"callback\",\"{0}\");\n",
_callbackPath);
builder.Append("so.write('player');\n");
builder.Append("</script>");
return builder.ToString();
}
}
Once the Mp3Player control was established we place the player anywhere we want in our pages and define properties cleanly within ASP.NET:
Using the Control
<custom:Mp3Player ID="Player" runat="server" Playlist="../scripts/GetPlaylist.aspx"
EnableViewState="true"
Width="350" Height="200" BgColor="#000000" DisplayHeight="0" Repeat="true" Shuffle="false"
BackColor="0xFFFFFF" ForeColor="0x000000" DisplayThumbs="false" OverStretch="false" />
In earlier versions of the CMS, I used to use cycles in the scanner to cache XSPF playlists but now they're generated dynamically with a script written to do it. Basically when the user clicks on a playlist or loads a page where a player is being used the [UNIQUE] trackId values are saved in a client cookie, which the playlist generation script reads from.
Generating XSPF Playlists AD-Hoc
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
string playlistTrackIds = Convert.ToString(
Request.Cookies["playlist_tracks"].Value);
string[] trackIds = playlistTrackIds.Split(new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries);
List<int> trackIdList = new List<int>();
foreach (string trackId in trackIds)
trackIdList.Add(Convert.ToInt32(trackId));
IList<Track> tracks = TrackRepository.GetTracksById(trackIdList);
List<Track> sortedTracks = new List<Track>();
foreach (int trackId in trackIdList)
{
foreach (Track track in tracks)
{
if (track.Id == trackId)
{
sortedTracks.Add(track);
break;
}
}
}
StreamWriter writer = new StreamWriter(Response.OutputStream);
string playlistXml = Playlist.GenerateXml(sortedTracks);
writer.Write(playlistXml);
writer.Flush();
}
</script>
With the Mp3Player control and dynamic playlist generation we have the core processing engine for the solution. For reading & writing ID3Tags I'm using this ID3 library from HundredMilesSoftware.
Conclusion
We've recently released v2.0.0.0 to the public and it's getting a positive response so far. Please feel free to download, modify, improve, and participate in the project! A lot of people spent a lot of time on this project and I hope that everyone out there really enjoys it.
History
- Dumped the revised article; updated for v2.0.0.0
- Dumped the original article; updated for v1.5.0.3
- Updated article 03/07: entire code base re-written. Article depreciated
- Originally submitted 03-30-2007
- Online Demo is now up 04-01-2007
- Fixed broken links
- Updated article 06-14-2007