Click here to Skip to main content
Click here to Skip to main content

The WPF Podcatcher Series - Part 1 (Introducing Podder)

By , 6 Jan 2008
 
Podder_Screenshot.png

Introduction

Welcome to the first article in a series devoted to a WPF application I wrote, called Podder. Podder allows you to listen to podcasts streamed over the Web. The aim of this article is to introduce Podder and let people around the world start enjoying it. The goal of this entire series, however, is to review interesting aspects of Podder’s design and implementation.

At the time of this writing, the application is incomplete. There are still many features, both big and small, that I intend to add to Podder. However, it is far enough along at this point to get this series off the ground, so I decided that sooner is better than later. The file downloads, located at the top of this article, will most likely change every time there is a new article in the series. Podder is a living project.

Background

A podcast is like a radio show; it is a series of episodes recorded by the same person or people about some particular topic. It can contain any audio content, since it is just a regular audio file, such as MP3. An episode can contain interviews, monologues, music, the sound of one hand clapping, etc. Almost every podcast has an RSS feed associated with it, so that you can easily find all episodes in the podcast, and discover newly released episodes.

A podcatcher is a program that allows you to play podcast episodes. Most podcatchers allow you to subscribe to various podcast RSS feeds, and will automatically notify you when a new episode is available. Podder allows you to maintain a list of podcast RSS feeds, but does not yet provide any notifications when new episodes exist.

The Podder application was created after I had built two simple podcatcher prototypes; the first was in Silverlight and the other in WPF. Podder is much more sophisticated and feature-rich than those two prototypes, but they were excellent learning experiences necessary for me to figure out how to design Podder correctly.

Technologies Used

  • VS2008 & .NET 3.5
  • WPF
  • XLinq
  • C# 3.0

Podder Features

The application has the core features one might expect in a podcatcher, and a few extra bells and whistles that I enjoy using. Here is what you will find in Podder:

  • Store a list of podcast RSS feeds and view all available episodes
  • Stream episodes for immediate playback
  • Standard play, pause, and stop functionality
  • Seek through a podcast, like fast-forward and rewind
  • Indicate which episodes are your favorite, and view them in a “My Favorites” list. That list is persisted between runs of the application.
  • Remember which episodes you have already listened to because they are marked as “finished” when they complete. The list of finished episodes is persisted between runs.
  • Optionally play every episode in a podcast in an endless loop
  • Refresh the list of episodes in a podcast, which is useful for frequently updated podcast feeds
  • Open an episode in an external program (either a Web browser or an MP3 player, depending on the podcast)
  • Tooltip over episode item in ListView displays extra information such as publication date and file size
  • Window sizes, states, and positions persist between runs of the application
  • Various user interface settings persist between runs of the application
  • Pre-loaded with some podcasts, so new users can immediately start listening to episodes
  • Podder will eventually allow you to apply completely different user interfaces at runtime (a.k.a. “structural skinning”)

Picture Gallery

Here are some screenshots of Podder, using the default skin (at this point, it is the only skin). The first image shows the list of favorite episodes and a tooltip over an episode, displaying extra information about it.

Podder_Favorites.png

The next image shows the list of podcasts I have entered into Podder. Notice that the first item in the dropdown allows you to view the list of favorite episodes.

Podder_Podcasts.png

The screenshot below shows the context menu that appears when you right-click on an item in the ListView. The menu item allows you to open an episode in a Web browser or MP3 player, based on the URL provided in the podcast RSS feed’s <link> element. If it is a Web page URL, the page will open in your default browser. If it points directly to an audio file, it will open in your default audio player (such as Windows Media Player).

Podder_OpenInExternalProgram.png

The last image shows the dialog box used to add and remove podcasts to the application’s list of podcast RSS feeds. At the time of this writing, this dialog box does not provide any validation, but it will in a subsequent release.

Podder_ManagePodcasts.png

Points of Interest

There are many interesting things to explore in Podder. This section lists some of them, and the next articles in the series explore some of them in detail.

Structural Skinning

Podder supports what I call “structural skinning”. Structural skinning allows you to apply a completely new user interface to the application at runtime. You can replace all of the controls in the UI, as well as the color schemes, fonts, etc. It uses the Model-View-Controller (MVC) pattern, routed commands, collection views, and dynamic resource references to make this possible. A future article in this series will review this topic in depth. As of this writing, Podder does not have any additional skins, but it will soon.

EpisodePlayer

The application uses WPF’s MediaPlayer class to play episode MP3 files. I subclassed MediaPlayer to create EpisodePlayer, which encapsulates MediaPlayer manipulation and workaround for its quirks. A disproportionately large amount of my development effort was spent on working around strange behavior of MediaPlayer. I have ironed out all the issues, as best as I can.

Reading an RSS Feed with XLinq

A podcast’s RSS feed is just like any RSS feed, it is just an XML document. Since Podder was written against the .NET 3.5 Framework, I used XLinq to transform an RSS feed into instances of my Episode class. Using XLinq made the XML processing much easier.

Serializable Data Model

Podder does not use a database or XML file to save its data. When the window closes, it serializes the data model using BinaryFormatter and saves that binary stream to disk. When the application opens again, it deserializes the data in that file and the application then uses those objects.

The application’s domain classes are in the PodderLib project. Most of those classes derive from my BindableObject base class, which provides an implementation of INotifyPropertyChanged, amongst other things.

Persisted Window Settings

Both the main window and the “Manage Podcasts” dialog box remember their size, state, and location between runs of the app. They both derive from my ConfigurableWindow base class to gain that functionality for free. That class was built specifically for Podder, but can be used in any WPF program.

So Much More…

It is difficult to create a list of “interesting” aspects of an application’s code base, because everyone sees things differently. If you are interested in seeing what else Podder has to offer, I suggest you download the source code and explore it on your own. If you have any questions, feel free to leave a question or comment on this article’s message board.

Not Yet Implemented

  • Ability to dynamically discover and apply external skins
  • Awareness of an active Internet connection, or lack thereof
  • An improved Podcast management dialog
  • User-friendly error handling
  • An aesthetically pleasing UI
  • Notifications of when a new episode exists in one of your feeds
  • A whole lot more!

Special Thanks

Craig Shoemaker, the host of Polymorphic Podcast, provided a lot of helpful feedback, insightful suggestions, and encouragement.

Karl Shifflett helped a lot with testing and submitting bug reports. He also gave some tasteful feature requests.

Sacha Barber indirectly assisted because his article about XLinq helped me get up to speed with XLinq enough so that I could use it to parse RSS feeds.

Revision History

  • January 6, 2008 - Created the article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Member
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
General[Message Removed]membernompel20 Sep '08 - 14:57 
Spam message removed
NewsPodder v2 Has Been Released!mvpJosh Smith6 Mar '08 - 9:26 
The second article[^] in this series contains Podder v2. It is much more feature-rich and robust than Podder v1. Go get it! Smile | :)
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralQuestion on Data Binding Usage [modified]memberaerospaceboy3 Feb '08 - 9:37 
I really appreciate the work you have done here, but just wanted to ask one question. I am curious as to why you store selected item information in the data model? This doesn't seem to be necessary, although since I do not know every implication of the decision I may be overlooking something.
 
For example, in the MainWindowView_Episodes.xaml under the _podcastListView declaration you set the Path of the DataContext Binding to EpisodePlayerSettings.SelectedPodcast when you could of set it to Podcasts.CurrentItem.
 
This would utilize the ICollectionView's management of CurrentItem instead of tracking that information in the data model.
 
I also noticed that with this change, and the removal of the line setting this property in the OnCurrentPodcastChanged method of the MainWindowController class, the property can be removed altogether from the data model.
 
Thanks again.
 
modified on Sunday, February 03, 2008 3:58:29 PM

GeneralRe: Question on Data Binding UsagemvpJosh Smith3 Feb '08 - 10:01 
That's a great question. The truth is, I did not know that binding to Podcasts.CurrentItem was possible. I was not aware that the XAML parser allows you to specify an ICollectionView property on the collection object it wraps. If I had known that, I probably would not have placed SelectedPodcast in the data model. Thanks for teaching me about that! Big Grin | :-D
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralCool....membermarlongrech7 Jan '08 - 11:33 
once again Josh does it.... Smile | :) nice one...
 
Regards
C# Disciple

GeneralRe: Cool....mvpJosh Smith7 Jan '08 - 11:42 
Thanks Marlon. In the immortal words of Randy Bachman, "You ain't seen nothing yet." This series is just getting started!! Cool | :cool:
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: Cool....membermarlongrech7 Jan '08 - 12:03 
I really like how you implemented the MVC with Commands.... It is really cool... It would be nice to see a futher article on that from you Smile | :)
 
Great job keep it up
 
Regards
C# Disciple

GeneralNice but...memberRama Krishna Vavilala7 Jan '08 - 3:47 
Overall, I liked the MVC design. Though, I normally prefer to have no code in the view classes (only markup).
 
Well sorry to be -ve but there are some aspects that I am not really crazy about in the code:
 
1. The error messages are in the Settings file instead of the resources file.
 
2. I am not crazy about deriving everything from the BindableObject class. This seems to be derivation just to add some functionality to the sub classes rather than due to a relationship.
 
Overall a nice article as usual.Smile | :)
GeneralRe: Nice but... [modified]mvpKarl Shifflett7 Jan '08 - 5:20 
Rama,
 
How's everything?
 
Got a question about point #2. I'm trying to learn a better way here.
 
If you have a base class like BindableObject, and want to add its functionality to subclasses as opposed to relationship building, what is the "Rama Approved" method for doing this?
 
In my WPF projects, my BLL entity objects all inherit from my BindableValidatableBase. There is no Goldfish is a Fish relationship, but the Customer, Vendor, Account entity classes need the built in functionality of this base class.
 
I looked at Delegation, but this one base class is much simpler and doesn't break polymorphic rules.
 
Thanks for your thoughts.
 
Cheers, Karl
 
My Blog | Mole's Home Page | Choosing WPF over ASP.NET
 

Just a grain of sand on the worlds beaches.

modified on Monday, January 07, 2008 12:05:35 PM

GeneralRe: Nice but...mvpJosh Smith7 Jan '08 - 5:57 
Thanks for the feedback, Rama. Please help me understand your points more clearly.
 
Rama Krishna Vavilala wrote:
1. The error messages are in the Settings file instead of the resources file.

 
What's wrong with putting them in the Settings file? What am I missing here?
 

Rama Krishna Vavilala wrote:
2. I am not crazy about deriving everything from the BindableObject class. This seems to be derivation just to add some functionality to the sub classes rather than due to a relationship.

 
BindableObject implements the all-important INotifyPropertyChanged interface, which is a must for any objects being bound to the UI. Since most of the classes in my data model are bound to, I need them to implement that interface. So, I put the interface implementation into a common base class. What's wrong with that?
 
Thanks.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralI like the bindable object ideamvpSacha Barber7 Jan '08 - 7:29 
To be honest Josh, I like the bindable object idea so much, im going to use right now on my next article.
 
I think its a good way to be honest. It does all the ground work. I like that
 
Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same view, and never argue
 
My Blog : sachabarber.net

GeneralRe: I like the bindable object ideamvpJosh Smith7 Jan '08 - 7:50 
Sacha Barber wrote:
I think its a good way to be honest.

 
Me too. It makes perfect sense to me.
 
Another nice feature of BindableObject is that if you call the RaisePropertyChanged method and pass in a property name that does not match any public property on the object, it will cause an error. This only happens when running a Debug build, not in Release. This feature has saved me hours of debugging time after renaming a property, but forgetting to update the property name string used in the PropertyChangedEventArgs.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I like the bindable object ideamvpSacha Barber7 Jan '08 - 9:14 
Yep I like that too.
 
What I dont like is using 3rd party web services, which I am for current article, and they throw some null value, every so often. Can I find it, no I cant.
 
Grrr
 
Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same view, and never argue
 
My Blog : sachabarber.net

GeneralRe: I like the bindable object ideamemberroland rodriguez7 Jan '08 - 11:06 
Hey Josh,
 
Looking forward to the rest of the articles. Regarding the BindableObject's RaisePropertyChanged method, does that work only with typed properties on the object's interface, or can it work on dynamic properties passed in a property bag like structure with name/value pairs like you guys use in Tangerine?
 
Regards,
 
Roland Rodriguez
GeneralRe: I like the bindable object ideamvpJosh Smith7 Jan '08 - 11:14 
roland rodriguez wrote:
Regarding the BindableObject's RaisePropertyChanged method, does that work only with typed properties on the object's interface, or can it work on dynamic properties passed in a property bag like structure with name/value pairs like you guys use in Tangerine?

 
The real question is, does WPF's data binding infrastructure support property bags. The answer is no, it only binds to and detects changes on "real" properties of an object. So, using BindableObject for an object whose property's are in a property bag would not work out.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I like the bindable object ideamemberroland rodriguez7 Jan '08 - 11:19 
According to MS the WPF databinding engine is supposed to recognise ICustomTypeDescriptor though so exposing the property bag via that should circumvent that, no?
 
Thanks again for another great article.
 
R
GeneralRe: I like the bindable object ideamvpJosh Smith7 Jan '08 - 11:25 
roland rodriguez wrote:
According to MS the WPF databinding engine is supposed to recognise ICustomTypeDescriptor though so exposing the property bag via that should circumvent that, no?

 
Oh, I see what you're getting at now. I have never tested BindableObject with a child class that uses ICustomTypeDescriptor to expose a property bag, so I don't know if it works. But, once Rama's fix is in place, I don't see why it would not work.
 
Thanks.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I like the bindable object ideamemberMaximilian Hänel12 Apr '08 - 9:13 
To turn it the other way: Why not deriving from DependencyObject object?
I'm currently playing with the MVC pattern and deriving from DependencyObject gives me the advantage of being able to implement Dependency properties (instead of INotifyPropertyChanged) in my "controllers". Another nice thing is that you can implement asynchronous methods without loosing the GUI thread affinity by means of Dispatcher.Invoke. Basically like this:
public bool IsActionRunning
{
    get { return (bool)GetValue(IsActionRunningProperty); }
    private set { SetValue(IsActionRunningPropertyKey, value); }
}
 
void OnCommandActionExecuted(object sender, ExecutedRoutedEventArgs e)
{
    IsActionRunning=true;//can be used to bind an animation
    Action<actionargs> action=AndAction;
    
    action.BeginInvoke(...)
}
 
void AndAction(ActionArgs args)
{
    // long running task 
    ...
    // now call back to the gui thread   
    Dispatcher.Invoke(
            DispatcherPriority.Normal,
            new Action<actionresultcollection>(ActionCompletedCompleted),
            qs);
}
 
void ActionCompletedCompleted(ActionResultCollection result)
{
    //Back in GUI Thread
    IsActionRunning=false;
    //Store results or whatever    
}
</actionresultcollection></actionargs>
//eop
 
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler

GeneralRe: I like the bindable object ideamvpJosh Smith12 Apr '08 - 9:18 
I use BinaryFormatter to save the app state, so I need my domain objects to be serializable. DependencyObject is not serializable.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I like the bindable object ideamemberMaximilian Hänel14 Apr '08 - 1:28 
I'm curious why you do not use XamlReader /XamlWriter for this purpose? It should work perfectly with DependencyObject derivates.
 
cu
 
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler

GeneralRe: I like the bindable object ideamvpJosh Smith15 Apr '08 - 5:06 
Maximilian Hänel wrote:
I'm curious why you do not use XamlReader /XamlWriter for this purpose? It should work perfectly with DependencyObject derivates.

 
Yes, but it is much slower. XamlWriter is notoriously slow. BinaryFormatter is fast.
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I like the bindable object ideamemberMaximilian Hänel15 Apr '08 - 5:09 
Thanks for answering!
 
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler

GeneralRe: Nice but...memberRama Krishna Vavilala7 Jan '08 - 9:23 
Josh Smith wrote:
What's wrong with putting them in the Settings file?

 
Simple reason this is not a setting. Settings are more for user configurable items at runtime: like window height, width (as you have correctly used). However error messages are not user configured. It does not make any sense for the user to modify the error message text. BTW: the app settings file get saved in configuration file and the user settings in a similar xml file in the user profile directory. Also when you think about localization resources make more sense.
 
Josh Smith wrote:
BindableObject implements the all-important INotifyPropertyChanged

 
There is nothing wrong per se. It's a more nit picky stuff. I will give a detailed reply a little later. Unless Marc jumps in and puts his take on it.
GeneralRe: Nice but...mvpJosh Smith7 Jan '08 - 9:35 
Rama Krishna Vavilala wrote:
Simple reason this is not a setting.

 
Good point. I guess I used the settings file because VS generates some classes that you can use to easily access those values. I never checked to see if the Resources does that, too, but all my experiences with resources are a pain involving ResourceManager, resource IDs, etc. A real nuisance. Using the Settings file is quick and easy. I'm lazy.

Rama Krishna Vavilala wrote:
Also when you think about localization resources make more sense.

 
Good point.
 

Rama Krishna Vavilala wrote:
There is nothing wrong per se. It's a more nit picky stuff. I will give a detailed reply a little later. Unless Marc jumps in and puts his take on it.

 
I assume you're going to go into the "IS A" relationship, etc. If so, don't bother. I know all about that, and long ago decided that object orientation IS A tool I use to HAVE AN easier time building software. I'm not into the whole philosophical debate of conceptual purity, etc. It's interesting stuff, but I'm not really interested in adhering to anything besides getting the job done. If I wrongly assumed your objection to my BindableObject class, please feel free to fire away with what you have in mind.
 
Thanks. Smile | :)
 
:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: Nice but...memberRama Krishna Vavilala7 Jan '08 - 9:51 
Josh Smith wrote:
I never checked to see if the Resources does that

 
Starting with VS 2005, accessing resources extremely easy. You can just do the following:
 
Resources.ErrorMessage. VS 2005 creates a type safe way to access respources. That's hwta you miss for programming in WPF and not WInForms Wink | ;)
 
Josh Smith wrote:
whole philosophical debates of conceptual purity

 
That's why I called it nit-picky and not a big deal. The issue here is that BindableObject works well for your code. But in complex hierarchies it is really not possible to have a base classes for interface implementation. So I agree it is not a big deal.
 
But here are some other minor stuff. I normally prefer to do it the standard way
 
virtual void OnPropertyChanged(PropertyChangedEventArgs e);
 
..insteda of Raise and After method.
 
Also I suggest a minor change to the VerifyProperty method(it is used only in debug so not a big deal again):
 
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
    if (TypeDescriptor.GetProperties(this).Find(propertyName) == null)
    {
        // The property could not be found,
        // so alert the developer of the problem.

        string msg = string.Format(
            ERROR_MSG,
            propertyName,
            type.FullName);
 
        Debug.Fail(msg);
    }
}
 
That way even the custom properties can be accounted for.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 6 Jan 2008
Article Copyright 2008 by Josh Smith
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid