diff --git a/examples/server-plugins/C/example-root-container.c b/examples/server-plugins/C/example-root-container.c
index 0d395ab..f3517b1 100644
--- a/examples/server-plugins/C/example-root-container.c
+++ b/examples/server-plugins/C/example-root-container.c
@@ -44,14 +44,14 @@ rygel_example_root_container_construct (GType object_type, const gchar *title) {
   self = (RygelExampleRootContainer*) rygel_simple_container_construct_root (object_type, title);
 
   item = rygel_music_item_new ("test 1", (RygelMediaContainer*) self, "Test 1", RYGEL_MUSIC_ITEM_UPNP_CLASS);
-  rygel_media_item_add_uri (item, "file:///home/murrayc/Music/Madness/05_Baggy_Trousers.mp3");
-  rygel_media_item_set_mime_type (item, "audio/mpeg");
+  rygel_media_file_item_add_uri (item, "file:///home/murrayc/Music/Madness/05_Baggy_Trousers.mp3");
+  rygel_media_file_item_set_mime_type (item, "audio/mpeg");
   rygel_simple_container_add_child_item ((RygelSimpleContainer*) self, item);
   g_object_unref (item);
   
   item = rygel_music_item_new ("test 2", (RygelMediaContainer*) self, "Test 1", RYGEL_MUSIC_ITEM_UPNP_CLASS);
-  rygel_media_item_add_uri (item, "file:///home/murrayc/Music/08%20Busload%20of%20Faith.mp3");
-  rygel_media_item_set_mime_type (item, "audio/mpeg");
+  rygel_media_file_item_add_uri (item, "file:///home/murrayc/Music/08%20Busload%20of%20Faith.mp3");
+  rygel_media_file_item_set_mime_type (item, "audio/mpeg");
   rygel_simple_container_add_child_item ((RygelSimpleContainer*) self, item);
   g_object_unref (item);
 
diff --git a/examples/standalone-server.c b/examples/standalone-server.c
index dd9a6a3..5f5d2f2 100644
--- a/examples/standalone-server.c
+++ b/examples/standalone-server.c
@@ -122,7 +122,7 @@ int main (int argc, char *argv[])
             RygelMediaObject *object;
             GeeArrayList* uris;
 
-            rygel_media_item_set_mime_type (RYGEL_MEDIA_ITEM (item), content_type);
+            rygel_media_file_item_set_mime_type (RYGEL_MEDIA_ITEM (item), content_type);
 
             object = RYGEL_MEDIA_OBJECT (item);
             gee_collection_add (GEE_COLLECTION (object->uris), uri);
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index f40d699..5f7d2a6 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -13,6 +13,7 @@ LIBRYGEL_SERVER_VAPI_SOURCE_FILES = \
 	rygel-thumbnail.vala \
 	rygel-video-item.vala \
 	rygel-media-container.vala \
+	rygel-media-file-item.vala \
 	rygel-media-item.vala \
 	rygel-media-object.vala \
 	rygel-media-server-plugin.vala \
diff --git a/src/librygel-server/rygel-audio-item.vala b/src/librygel-server/rygel-audio-item.vala
index 5882ea5..7eb0287 100644
--- a/src/librygel-server/rygel-audio-item.vala
+++ b/src/librygel-server/rygel-audio-item.vala
@@ -1,9 +1,12 @@
 /*
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2010 Nokia Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
+ *         Doug Galligan <doug@sentosatech.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -25,16 +28,39 @@
 using GUPnP;
 
 /**
- * Represents an audio item.
+ * Represents an audio item contained in a file.
  */
-public class Rygel.AudioItem : MediaItem {
+public class Rygel.AudioItem : MediaFileItem {
     public new const string UPNP_CLASS = "object.item.audioItem";
 
-    public long duration { get; set; default = -1; }  // Duration in seconds
-    public int bitrate { get; set; default = -1; }    // Bytes/second
+    /**
+     * The duration of the source content (this.uri) in seconds.
+     * A value of -1 means the duration is unknown
+     */
+    public long duration { get; set; default = -1; }
 
+    /**
+     * The bitrate of the source content (this.uri) in bytes/second.
+     * A value of -1 means the bitrate is unknown
+     */
+    public int bitrate { get; set; default = -1; }
+
+    /**
+     * The sample frequency of the source content (this.uri) in Hz.
+     * A value of -1 means the sample frequency is unknown
+     */
     public int sample_freq { get; set; default = -1; }
+
+    /**
+     * The bits per sample of the source content (this.uri).
+     * A value of -1 means the bits per sample is unknown
+     */
     public int bits_per_sample { get; set; default = -1; }
+
+    /**
+     * The number of audio channels in the source content (this.uri).
+     * A value of -1 means the number of channels is unknown
+     */
     public int channels { get; set; default = -1; }
 
     public AudioItem (string         id,
diff --git a/src/librygel-server/rygel-http-post.vala b/src/librygel-server/rygel-http-post.vala
index f2b598b..c0b4181 100644
--- a/src/librygel-server/rygel-http-post.vala
+++ b/src/librygel-server/rygel-http-post.vala
@@ -58,13 +58,13 @@ internal class Rygel.HTTPPost : HTTPRequest {
     }
 
     private async void handle_real () throws Error {
-        if (!(this.object as MediaItem).place_holder) {
+        if (!(this.object as MediaFileItem).place_holder) {
             var msg = _("Pushing data to non-empty item '%s' not allowed");
 
             throw new ContentDirectoryError.INVALID_ARGS (msg, this.object.id);
         }
 
-        this.file = yield (this.object as MediaItem).get_writable
+        this.file = yield (this.object as MediaFileItem).get_writable
                                         (this.cancellable);
         if (this.file == null) {
             throw new HTTPRequestError.BAD_REQUEST
@@ -109,13 +109,13 @@ internal class Rygel.HTTPPost : HTTPRequest {
     private async void wait_for_item (MediaContainer container,
                                       string         id,
                                       uint           timeout) {
-        MediaItem item = null;
+        MediaFileItem item = null;
 
         while (item == null || item.place_holder) {
             try {
                 item = (yield container.find_object (id,
                                                      this.cancellable))
-                                                     as MediaItem;
+                                                     as MediaFileItem;
             } catch (Error error) {
                 // Handle
                 break;
@@ -218,7 +218,7 @@ internal class Rygel.HTTPPost : HTTPRequest {
 
     private async void remove_item () {
         var queue = ObjectRemovalQueue.get_default ();
-        yield queue.remove_now (this.object as MediaItem, null);
+        yield queue.remove_now (this.object as MediaFileItem, null);
     }
 
     private void disconnect_message_signals () {
diff --git a/src/librygel-server/rygel-http-request.vala b/src/librygel-server/rygel-http-request.vala
index 9b77f04..36445fc 100644
--- a/src/librygel-server/rygel-http-request.vala
+++ b/src/librygel-server/rygel-http-request.vala
@@ -1,10 +1,12 @@
 /*
  * Copyright (C) 2008-2010 Nokia Corporation.
  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
  *         Jorn Baayen <jorn.baayen@gmail.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -89,10 +87,7 @@ internal abstract class Rygel.HTTPRequest : GLib.Object, Rygel.StateMachine {
         var media_object = yield this.root_container.find_object
                                         (this.uri.item_id, null);
 
-        if (media_object == null ||
-            !((media_object is MediaContainer &&
-               this.uri.playlist_format != null) ||
-              (media_object is MediaItem && this.uri.playlist_format == null))) {
+        if (media_object == null) {
             throw new HTTPRequestError.NOT_FOUND
                                         (_("Requested item '%s' not found"),
                                          this.uri.item_id);
diff --git a/src/librygel-server/rygel-image-item.vala b/src/librygel-server/rygel-image-item.vala
index 55503c6..6a8fe7f 100644
--- a/src/librygel-server/rygel-image-item.vala
+++ b/src/librygel-server/rygel-image-item.vala
@@ -2,9 +2,12 @@
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2010 Nokia Corporation.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
+ *         Doug Galligan <doug@sentosatech.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -29,7 +32,7 @@ using Gee;
 /**
  * Represents an image item.
  */
-public class Rygel.ImageItem : MediaItem, VisualItem {
+public class Rygel.ImageItem : MediaFileItem, VisualItem {
     public new const string UPNP_CLASS = "object.item.imageItem";
 
     //TODO: This property documentation is not used.
diff --git a/src/librygel-server/rygel-import-resource.vala b/src/librygel-server/rygel-import-resource.vala
index 2ecdbc2..56ee99d 100644
--- a/src/librygel-server/rygel-import-resource.vala
+++ b/src/librygel-server/rygel-import-resource.vala
@@ -48,7 +48,7 @@ internal class Rygel.ImportResource : GLib.Object, Rygel.StateMachine {
     public int64 bytes_copied;
     public int64 bytes_total;
 
-    private MediaItem item;
+    private MediaFileItem item;
     private Session session;
 
     public string status_as_string {
@@ -166,7 +166,7 @@ internal class Rygel.ImportResource : GLib.Object, Rygel.StateMachine {
         this.completed ();
     }
 
-    private async MediaItem fetch_item () throws Error {
+    private async MediaFileItem fetch_item () throws Error {
         HTTPItemURI uri;
         try {
             uri = new HTTPItemURI.from_string (this.destination_uri,
@@ -180,11 +180,11 @@ internal class Rygel.ImportResource : GLib.Object, Rygel.StateMachine {
         string msg = null;
 
         if (media_object == null ||
-            !(media_object is MediaItem) ||
+            !(media_object is MediaFileItem) ||
             !(media_object.parent is WritableContainer)) {
             msg = _("URI '%s' invalid for importing contents to").printf
                                         (this.destination_uri);
-        } else if (!(media_object as MediaItem).place_holder) {
+        } else if (!(media_object as MediaFileItem).place_holder) {
             msg = _("Pushing data to non-empty item '%s' not allowed").printf
                                         (media_object.id);
         } else if (media_object.uris.size < 1) {
@@ -195,7 +195,7 @@ internal class Rygel.ImportResource : GLib.Object, Rygel.StateMachine {
             throw new ContentDirectoryError.INVALID_ARGS (msg);
         }
 
-        return media_object as MediaItem;
+        return media_object as MediaFileItem;
     }
 
     private void got_headers_cb (Message message) {
diff --git a/src/librygel-server/rygel-item-destroyer.vala b/src/librygel-server/rygel-item-destroyer.vala
index 846fd2a..93e8f18 100644
--- a/src/librygel-server/rygel-item-destroyer.vala
+++ b/src/librygel-server/rygel-item-destroyer.vala
@@ -77,10 +77,10 @@ internal class Rygel.ItemDestroyer: GLib.Object, Rygel.StateMachine {
         var media_object = yield this.fetch_object ();
         var parent = media_object.parent as WritableContainer;
 
-        if (media_object is MediaItem ) {
+        if (media_object is MediaFileItem ) {
             yield parent.remove_item (this.object_id, this.cancellable);
 
-            if (!(media_object as MediaItem).place_holder) {
+            if (!(media_object as MediaFileItem).place_holder) {
                 var writables = yield media_object.get_writables (this.cancellable);
                 foreach (var file in writables) {
                     if (file.query_exists (this.cancellable)) {
diff --git a/src/librygel-server/rygel-lg-tv-hacks.vala b/src/librygel-server/rygel-lg-tv-hacks.vala
index a24ed65..ab8ff97 100644
--- a/src/librygel-server/rygel-lg-tv-hacks.vala
+++ b/src/librygel-server/rygel-lg-tv-hacks.vala
@@ -31,11 +31,11 @@ internal class Rygel.LGTVHacks : ClientHacks {
     }
 
     public override void apply (MediaObject object) {
-        if (!(object is MediaItem)) {
+        if (!(object is MediaFileItem)) {
             return;
         }
 
-        var item = object as MediaItem;
+        var item = object as MediaFileItem;
         if (item.mime_type == "audio/x-vorbis+ogg" ||
             item.mime_type == "audio/x-flac+ogg") {
             item.mime_type = "application/ogg";
diff --git a/src/librygel-server/rygel-media-container.vala b/src/librygel-server/rygel-media-container.vala
index 2c290a7..dec001f 100644
--- a/src/librygel-server/rygel-media-container.vala
+++ b/src/librygel-server/rygel-media-container.vala
@@ -3,9 +3,11 @@
  * Copyright (C) 2010 MediaNet Inh.
  * Copyright (C) 2010 Nokia Corporation.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Authors: Zeeshan Ali <zeenix@gmail.com>
  *          Sunil Mohan Adapa <sunil@medhas.org>
+ *          Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -75,6 +77,9 @@ public abstract class Rygel.MediaContainer : MediaObject {
                                               "+upnp:originalTrackNumber," +
                                               "+dc:title";
 
+    private const string DIDL_S_PLAYLIST_RESNAME = "dldl_s_playlist";
+    private const string M3U_PLAYLIST_RESNAME = "m3u_playlist";
+
     /* TODO: When we implement ContentDirectory v4, this will be emitted also
      * when child _items_ are updated.
      */
diff --git a/src/librygel-server/rygel-media-file-item.vala b/src/librygel-server/rygel-media-file-item.vala
new file mode 100644
index 0000000..dcfbb79
--- /dev/null
+++ b/src/librygel-server/rygel-media-file-item.vala
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ *                               <zeeshan.ali@nokia.com>
+ *         Doug Galligan <doug@sentosatech.com>
+ *         Craig Pratt <craig@ecaspia.com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GUPnP;
+
+private errordomain Rygel.MediaFileItemError {
+    BAD_URI
+}
+
+/**
+ * Represents a file-accessible or http-accessible media item (music file, image file,
+ * video file, etc) with some pre-established metadata or a content placeholder for
+ * uploaded content.
+ *
+ * Items representing non-file-accessible content should create their own MediaItem subclass.
+ */
+public abstract class Rygel.MediaFileItem : MediaItem {
+    /**
+     * The mime type of the source content (this.uri).
+     * A null/empty value means that the mime-type is unknown
+     */
+    public string mime_type { get; set; }
+
+    /**
+     * The DLNA profile of the source content (this.uri).
+     * A null/empty value means that the DLNA profile is unknown
+     */
+    public string dlna_profile { get; set; }
+
+    /**
+     * The size of the source content (this.uri).
+     * A value of -1 means that the size is unknown
+     */
+    private int64 _size = -1;
+    public int64 size {
+        get {
+            return this._size;
+        }
+
+        set {
+            if (value == 0) {
+                this.place_holder = true;
+            }
+
+            this._size = value;
+        }
+    }   // Size in bytes
+
+    public bool place_holder { get; set; default = false; }
+
+    public override OCMFlags ocm_flags {
+        get {
+            var flags = OCMFlags.NONE;
+
+            if (this.place_holder) {
+                // Place-holder items are always destroyable.
+                flags |= OCMFlags.DESTROYABLE;
+            } else {
+                var config = MetaConfig.get_default ();
+                var allow_deletion = true;
+
+                try {
+                    allow_deletion = config.get_allow_deletion ();
+                } catch (Error error) {}
+
+                if (allow_deletion) {
+                    flags |= OCMFlags.DESTROYABLE;
+                }
+            }
+
+            if (this is UpdatableObject) {
+                flags |= OCMFlags.CHANGE_METADATA;
+            }
+
+            return flags;
+        }
+    }
+
+    protected static Regex address_regex;
+
+    public MediaFileItem (string         id,
+                          MediaContainer parent,
+                          string         title,
+                          string         upnp_class) {
+        Object (id : id,
+                parent : parent,
+                title : title,
+                upnp_class : upnp_class);
+    }
+
+    public static Gee.HashMap<string, string> mime_to_ext;
+
+    static construct {
+        try {
+            address_regex = new Regex (Regex.escape_string ("@ADDRESS@"));
+        } catch (GLib.RegexError err) {
+            assert_not_reached ();
+        }
+    }
+
+    public override DataSource? create_stream_source_for_resource (HTTPRequest request,
+                                                                   MediaResource resource)
+        throws Error {
+        if (this.uris.size == 0) {
+            return null;
+        }
+
+        return MediaEngine.get_default ().create_data_source_for_resource (this, resource);
+    }
+
+    /**
+     * Subclasses override this method to do type-specific resource processing before serialize().
+     */
+    public virtual void add_uri (string uri) {
+        this.uris.add (uri);
+    }
+
+    internal override DIDLLiteObject? serialize (Serializer serializer,
+                                                 HTTPServer http_server)
+                                                 throws Error {
+        var didl_item = base.serialize (serializer, http_server) as DIDLLiteItem;
+
+        if (!this.place_holder) {
+            // Subclasses can override add_resources and augment the media resource list (which
+            //  should contain the primary resource representations for the MediaItem
+            //  at this point) with any secondary representations or alternate delivery
+            //  mechanisms they can provide
+            this.add_resources (http_server);
+
+            this.serialize_resource_list (didl_item, http_server);
+        }
+
+        return didl_item;
+    }
+
+    /**
+     * Subclasses override this method to create the type-specific primary MediaResource.
+     * 
+     * The resource returned is presumed to represent the "internal" file resource and
+     * a uri referring to the source file. Transport-specific variants can be created
+     * by the caller.
+     */
+    public virtual MediaResource get_primary_resource () {
+        var res = new MediaResource ("primary");
+
+        res.mime_type = this.mime_type;
+        res.dlna_profile = this.dlna_profile;
+        res.dlna_flags = DLNAFlags.BACKGROUND_TRANSFER_MODE;
+
+        // MediaFileItems refer directly to the source URI
+        res.uri = this.uris.get (0);
+        try {
+            res.protocol = this.get_protocol_for_uri (res.uri);
+        } catch (Error e) {
+            warning ("Could not determine protocol for " + res.uri);
+        }
+        res.extension = get_extension ();
+        res.size = this.size;
+        return res;
+    }
+
+    /**
+     * Return the file/uri extension that best represents the item's primary resource.
+     */
+    public virtual string get_extension () {
+        string uri_extension = null;
+        // Use the extension from the source content filename, if it has an extension
+        string basename = Path.get_basename (this.uris.get (0));
+        int dot_index = -1;
+        if (basename != null) {
+            dot_index = basename.last_index_of (".");
+            if (dot_index > -1) {
+               uri_extension = basename.substring (dot_index + 1);
+            }
+        }
+
+        if (uri_extension == null) {
+            uri_extension = ext_from_mime_type (this.mime_type);
+        }
+
+        return uri_extension;
+    }
+
+    protected string ext_from_mime_type (string mime_type) {
+        if (mime_to_ext == null) {
+            // Lazy initialization of the static hashmap
+            mime_to_ext = new Gee.HashMap<string, string> ();
+            // videos
+            string[] videos = {"mpeg", "webm", "ogg", "mp4"};
+
+            foreach (string video in videos) {
+                mime_to_ext.set ("video/" + video, video);
+            }
+            mime_to_ext.set ("video/x-matroska", "mkv");
+
+            // audios
+            mime_to_ext.set ("audio/x-wav", "wav");
+            mime_to_ext.set ("audio/x-matroska", "mka");
+
+            // images
+            string[] images = {"jpeg", "png"};
+
+            foreach (string image in images) {
+                mime_to_ext.set ("image/" + image, image);
+            }
+
+            // texts
+            mime_to_ext.set ("text/srt", "srt");
+            mime_to_ext.set ("text/xml", "xml");
+
+            // applications? (can be either video or audio?);
+            mime_to_ext.set ("application/ogg", "ogg");
+        }
+
+        if (MediaFileItem.mime_to_ext.has_key (mime_type)) {
+            return mime_to_ext.get (mime_type);
+        }
+
+        return "";
+    }
+
+    /**
+     * Subclasses can override this method to augment the MediaObject MediaResource
+     * list with secondary MediaResource objects representing derivative resources.
+     *
+     * Note: Implementations should add both internal/file-based resources and HTTP-accessible
+     *       resources to the MediaResource list.
+     */
+    internal abstract void add_resources (HTTPServer server);
+}
diff --git a/src/librygel-server/rygel-media-item.vala b/src/librygel-server/rygel-media-item.vala
index f175c79..88dc9ae 100644
--- a/src/librygel-server/rygel-media-item.vala
+++ b/src/librygel-server/rygel-media-item.vala
@@ -2,9 +2,12 @@
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2010 Nokia Corporation.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
+ *         Doug Galligan <doug@sentosatech.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -25,182 +28,72 @@
 
 using GUPnP;
 
-private errordomain Rygel.MediaItemError {
-    BAD_URI
-}
-
 /**
- * Represents a media item (Music, Video or Image).
+ * Abstract class representing a MediaItem
  *
- * These objects correspond to items in the UPnP ContentDirectory's DIDL-Lite XML.
+ * MediaItems must live in a container and may not contain other MediaItems
  */
 public abstract class Rygel.MediaItem : MediaObject {
-    public string date { get; set; }
-
-    public string creator { get; set; }
-
-    // Resource info
-    public string mime_type { get; set; }
-    public string dlna_profile { get; set; }
-
-    // Size in bytes
-    private int64 _size = -1;
-    public int64 size {
-        get {
-            return this._size;
-        }
-
-        set {
-            if (value == 0) {
-                this.place_holder = true;
-            }
-
-            this._size = value;
-        }
-    }   // Size in bytes
-
-    public bool place_holder { get; set; default = false; }
 
     public string description { get; set; default = null; }
 
-    public override OCMFlags ocm_flags {
-        get {
-            var flags = OCMFlags.NONE;
-
-            if (this.place_holder) {
-                // Place-holder items are always destroyable.
-                flags |= OCMFlags.DESTROYABLE;
-            } else {
-                var config = MetaConfig.get_default ();
-                var allow_deletion = true;
-
-                try {
-                    allow_deletion = config.get_allow_deletion ();
-                } catch (Error error) {}
-
-                if (allow_deletion) {
-                    flags |= OCMFlags.DESTROYABLE;
-                }
-            }
-
-            if (this is UpdatableObject) {
-                flags |= OCMFlags.CHANGE_METADATA;
-            }
-
-            return flags;
+    internal override int compare_by_property (MediaObject media_object,
+                                               string      property) {
+        if (!(media_object is MediaItem)) {
+           return 1;
         }
-    }
-
-    protected static Regex address_regex;
 
-    public MediaItem (string         id,
-                      MediaContainer parent,
-                      string         title,
-                      string         upnp_class) {
-        Object (id : id,
-                parent : parent,
-                title : title,
-                upnp_class : upnp_class);
-    }
+        var item = media_object as MediaItem;
 
-    static construct {
-        try {
-            address_regex = new Regex (Regex.escape_string ("@ADDRESS@"));
-        } catch (GLib.RegexError err) {
-            assert_not_reached ();
+        switch (property) {
+        case "dc:creator":
+            return this.compare_string_props (this.creator, item.creator);
+        case "dc:date":
+            return this.compare_by_date (item);
+        default:
+            return base.compare_by_property (item, property);
         }
     }
 
-    // Live media items need to provide a nice working implementation of this
-    // method if they can/do not provide a valid URI
-    public virtual DataSource? create_stream_source (string? host_ip = null) {
-        if (this.uris.size == 0) {
-            return null;
-        }
+    private int compare_by_date (MediaItem item) {
+        if (this.date == null) {
+            return -1;
+        } else if (item.date == null) {
+            return 1;
+        } else {
+            var our_date = this.date;
+            var other_date = item.date;
 
-        string translated_uri = this.uris.get (0);
-        if (host_ip != null) {
-            try {
-                translated_uri = MediaItem.address_regex.replace_literal
-                    (this.uris.get (0), -1, 0, host_ip);
-            } catch (Error error) {
-                assert_not_reached ();
+            if (!our_date.contains ("T")) {
+                our_date += "T00:00:00Z";
             }
-        }
 
-        return MediaEngine.get_default ().create_data_source (translated_uri);
-    }
-
-    public bool is_live_stream () {
-        return this.streamable () && this.size <= 0;
-    }
-
-    public abstract bool streamable ();
-
-    public virtual void add_uri (string uri) {
-        this.uris.add (uri);
-    }
-
-    internal int compare_transcoders (Transcoder transcoder1,
-                                      Transcoder transcoder2) {
-        return (int) transcoder1.get_distance (this) -
-               (int) transcoder2.get_distance (this);
-    }
+            if (!other_date.contains ("T")) {
+                other_date += "T00:00:00Z";
+            }
 
-    internal override DIDLLiteResource add_resource
-                                        (DIDLLiteObject didl_object,
-                                         string?        uri,
-                                         string         protocol,
-                                         string?        import_uri = null)
-                                         throws Error {
-        var res = base.add_resource (didl_object,
-                                     uri,
-                                     protocol,
-                                     import_uri);
+            var tv1 = TimeVal ();
+            assert (tv1.from_iso8601 (this.date));
 
-        if (uri != null && !this.place_holder) {
-            res.uri = uri;
-        } else {
-            // Set empty string otherwise gupnp-av (libxml actually) will add
-            // a self-enclosing node in the DIDL-Lite which is not very much
-            // appreciated by UPnP devices using crappy XML parsers.
-            res.uri = "";
-        }
+            var tv2 = TimeVal ();
+            assert (tv2.from_iso8601 (item.date));
 
-        if (import_uri != null && this.place_holder) {
-            res.import_uri = import_uri;
-        }
+            var ret = this.compare_long (tv1.tv_sec, tv2.tv_sec);
+            if (ret == 0) {
+                ret = this.compare_long (tv1.tv_usec, tv2.tv_usec);
+            }
 
-        if (this is TrackableItem) {
-            // This is attribute is mandatory for track changes
-            // implementation. We don't really support updating the resources
-            // so we just set it to 0.
-            res.update_count = 0;
+            return ret;
         }
-
-        res.size64 = this.size;
-
-        /* Protocol info */
-        res.protocol_info = this.get_protocol_info (uri, protocol);
-
-        return res;
     }
 
-    internal override int compare_by_property (MediaObject media_object,
-                                               string      property) {
-        if (!(media_object is MediaItem)) {
-           return 1;
-        }
-
-        var item = media_object as MediaItem;
-
-        switch (property) {
-        case "dc:creator":
-            return this.compare_string_props (this.creator, item.creator);
-        case "dc:date":
-            return this.compare_by_date (item);
-        default:
-            return base.compare_by_property (item, property);
+    private int compare_long (long a, long b) {
+        if (a < b) {
+            return -1;
+        } else if (a > b) {
+            return 1;
+        } else {
+            return 0;
         }
     }
 
diff --git a/src/librygel-server/rygel-media-object.vala b/src/librygel-server/rygel-media-object.vala
index 039e547..0defc03 100644
--- a/src/librygel-server/rygel-media-object.vala
+++ b/src/librygel-server/rygel-media-object.vala
@@ -1,8 +1,11 @@
 /*
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali <zeenix@gmail.com>
+ *         Doug Galligan <doug@sentosatech.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -27,11 +30,8 @@ using Gee;
 /**
  * Represents a media object (container or item).
  *
- * The derived RygelMediaContainer class represents a container,
- * and the derived RygelMediaItem classes (RygelAudioItem,
- * RygelImageItem and RygelVideoItem) represent media items.
- *
- * These objects correspond to items and containers in the UPnP ContentDirectory's DIDL-Lite XML.
+ * The derived RygelMediaContainer class represents a container
+ * and the derived MediaItem classes represent media items.
  */
 public abstract class Rygel.MediaObject : GLib.Object {
     private static Regex real_name_regex;
@@ -42,6 +42,8 @@ public abstract class Rygel.MediaObject : GLib.Object {
     public string id { get; set construct; }
     public string ref_id { get; set; }
     public string upnp_class { get; construct set; }
+    public string date { get; set; }
+    public string creator { get; set; }
     public uint64 modified { get; set; }
     public uint object_update_id { get; set; }
 
diff --git a/src/librygel-server/rygel-object-creator.vala b/src/librygel-server/rygel-object-creator.vala
index fcb1ecf..5622568 100644
--- a/src/librygel-server/rygel-object-creator.vala
+++ b/src/librygel-server/rygel-object-creator.vala
@@ -152,8 +152,8 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
             }
 
             yield this.create_object_from_didl (container);
-            if (this.object is MediaItem) {
-                yield container.add_item (this.object as MediaItem,
+            if (this.object is MediaFileItem) {
+                yield container.add_item (this.object as MediaFileItem,
                                           this.cancellable);
             } else {
                 yield container.add_container (this.object as MediaContainer,
@@ -168,8 +168,8 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
             this.conclude ();
 
             if (this.container_id == MediaContainer.ANY &&
-                (this.object is MediaItem && (this.object as
-                                              MediaItem).place_holder)) {
+                (this.object is MediaFileItem &&
+                 (this.object as MediaFileItem).place_holder)) {
                 var queue = ObjectRemovalQueue.get_default ();
 
                 queue.queue (this.object, this.cancellable);
@@ -474,11 +474,11 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
     }
 
     private string get_generic_mime_type () {
-        if (!(this.object is MediaItem)) {
+        if (!(this.object is MediaFileItem)) {
             return "";
         }
 
-        var item = this.object as MediaItem;
+        var item = this.object as MediaFileItem;
 
         if (item is ImageItem) {
             return "image";
@@ -506,7 +506,7 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
                                           this.didl_object.title,
                                           this.didl_object.upnp_class);
 
-        if (this.object is MediaItem) {
+        if (this.object is MediaFileItem) {
             this.extract_item_parameters ();
         }
 
@@ -514,13 +514,13 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
         if (this.object.uris.size == 0) {
             var uri = yield this.create_uri (container, this.object.title);
             this.object.uris.add (uri);
-            if (this.object is MediaItem) {
-                (this.object as MediaItem).place_holder = true;
+            if (this.object is MediaFileItem) {
+                (this.object as MediaFileItem).place_holder = true;
             }
         } else {
-            if (this.object is MediaItem) {
+            if (this.object is MediaFileItem) {
                 var file = File.new_for_uri (this.object.uris[0]);
-                (this.object as MediaItem).place_holder = !file.is_native ();
+                (this.object as MediaFileItem).place_holder = !file.is_native ();
             }
         }
 
@@ -530,7 +530,7 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
     }
 
     private void extract_item_parameters () throws Error {
-        var item = this.object as MediaItem;
+        var item = this.object as MediaFileItem;
 
         var resources = this.didl_object.get_resources ();
         if (resources != null && resources.length () > 0) {
@@ -585,9 +585,8 @@ internal class Rygel.ObjectCreator: GLib.Object, Rygel.StateMachine {
 
         var parsed_date = new Soup.Date.from_string (didl_item.date);
         if (parsed_date != null) {
-            (this.object as MediaItem).date = parsed_date.to_string
-                                            (Soup.DateFormat.ISO8601);
-
+            (this.object as MediaFileItem).date = parsed_date.to_string
+                                                   (Soup.DateFormat.ISO8601);
             return;
         }
 
diff --git a/src/librygel-server/rygel-panasonic-hacks.vala b/src/librygel-server/rygel-panasonic-hacks.vala
index e3bd7de..6a08ddb 100644
--- a/src/librygel-server/rygel-panasonic-hacks.vala
+++ b/src/librygel-server/rygel-panasonic-hacks.vala
@@ -43,11 +43,11 @@ internal class Rygel.PanasonicHacks : ClientHacks {
     }
 
     public override void apply (MediaObject object) {
-        if (!(object is MediaItem)) {
+        if (!(object is MediaFileItem)) {
             return;
         }
 
-        var item = object as MediaItem;
+        var item = object as MediaFileItem;
 
         if (!(item is VisualItem)) {
             return;
diff --git a/src/librygel-server/rygel-playlist-item.vala b/src/librygel-server/rygel-playlist-item.vala
index 4495941..1f5e415 100644
--- a/src/librygel-server/rygel-playlist-item.vala
+++ b/src/librygel-server/rygel-playlist-item.vala
@@ -27,7 +27,7 @@ using GUPnP;
  *
  * These objects correspond to DLNA's DIDL_S items.
  */
-public class Rygel.PlaylistItem : MediaItem {
+public class Rygel.PlaylistItem : MediaFileItem {
     public new const string UPNP_CLASS = "object.item.playlistItem";
 
     public PlaylistItem (string         id,
diff --git a/src/librygel-server/rygel-reference-creator.vala b/src/librygel-server/rygel-reference-creator.vala
index b976dd9..2672046 100644
--- a/src/librygel-server/rygel-reference-creator.vala
+++ b/src/librygel-server/rygel-reference-creator.vala
@@ -68,6 +68,13 @@ internal class Rygel.ReferenceCreator : GLib.Object, Rygel.StateMachine {
                                         (_("No such object"));
             }
 
+            if (!(container is WritableContainer)) {
+                        throw new ContentDirectoryError.RESTRICTED_PARENT
+                                                    (_("Object creation in %s not allowed"),
+                                                     container.id);
+            }
+            // FIXME: Check for @restricted=1 missing?
+
             var new_id = yield container.add_reference (object,
                                                         this.cancellable);
 
@@ -111,14 +118,8 @@ internal class Rygel.ReferenceCreator : GLib.Object, Rygel.StateMachine {
         if (media_object == null || !(media_object is MediaContainer)) {
             throw new ContentDirectoryError.NO_SUCH_CONTAINER
                                         (_("No such object"));
-        } else if (!(media_object is WritableContainer)) {
-            throw new ContentDirectoryError.RESTRICTED_PARENT
-                                        (_("Object creation in %s not allowed"),
-                                         media_object.id);
         }
 
-        // FIXME: Check for @restricted=1 missing?
-
         return media_object as WritableContainer;
     }
 
diff --git a/src/librygel-server/rygel-samsung-tv-hacks.vala b/src/librygel-server/rygel-samsung-tv-hacks.vala
index 83ea363..ee410b7 100644
--- a/src/librygel-server/rygel-samsung-tv-hacks.vala
+++ b/src/librygel-server/rygel-samsung-tv-hacks.vala
@@ -29,11 +29,11 @@ internal class Rygel.SamsungTVHacks : ClientHacks {
     }
 
     public override void apply (MediaObject object) {
-        if (!(object is MediaItem)) {
+        if (!(object is MediaFileItem)) {
             return;
         }
 
-        var item = object as MediaItem;
+        var item = object as MediaFileItem;
         if (item.mime_type == "video/x-matroska") {
             item.mime_type = "video/x-mkv";
         }
diff --git a/src/librygel-server/rygel-video-item.vala b/src/librygel-server/rygel-video-item.vala
index 488f6eb..e517b5e 100644
--- a/src/librygel-server/rygel-video-item.vala
+++ b/src/librygel-server/rygel-video-item.vala
@@ -2,10 +2,12 @@
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2010 Nokia Corporation.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
  *         Jens Georg <jensg@openismus.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -39,20 +41,20 @@ public class Rygel.VideoItem : AudioItem, VisualItem {
     //See valadoc bug: https://bugzilla.gnome.org/show_bug.cgi?id=684367
 
     /**
-     * The width of the item in pixels.
-     * A value of -1 means that the width is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The width of the item source content (this.uri) in pixels
+     * A value of -1 means that the width is unknown
      */
     public int width { get; set; default = -1; }
 
     /**
-     * The height of the item in pixels.
-     * A value of -1 means that the height is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The height of the item source content (this.uri) in pixels
+     * A value of -1 means that the height is unknown
      */
     public int height { get; set; default = -1; }
 
     /**
-     * The number of bits per pixel used to represent the video resource.
-     * A value of -1 means that the color depth is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The number of bits per pixel in the source video resource (this.uri)
+     * A value of -1 means that the color depth is unknown
      */
     public int color_depth { get; set; default = -1; }
 
diff --git a/src/librygel-server/rygel-visual-item.vala b/src/librygel-server/rygel-visual-item.vala
index 82a792e..a4d301c 100644
--- a/src/librygel-server/rygel-visual-item.vala
+++ b/src/librygel-server/rygel-visual-item.vala
@@ -2,10 +2,12 @@
  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
  * Copyright (C) 2010 Nokia Corporation.
  * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
  *                               <zeeshan.ali@nokia.com>
  *         Jens Georg <jensg@openismus.com>
+ *         Craig Pratt <craig@ecaspia.com>
  *
  * This file is part of Rygel.
  *
@@ -28,25 +30,25 @@ using GUPnP;
 using Gee;
 
 /**
- * An interface that visual (video and image) items must implement.
+ * An interface representing visual properties of an item stored in a file.
  */
-public interface Rygel.VisualItem : MediaItem {
+public interface Rygel.VisualItem : MediaFileItem {
 
     /**
-     * The width of the item in pixels.
-     * A value of -1 means that the width is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The width of the source content (this.uri) in pixels.
+     * A value of -1 means that the width is unknown
      */
     public abstract int width { get; set; }
 
     /**
-     * The height of the item in pixels.
-     * A value of -1 means that the height is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The height of the source content (this.uri) in pixels.
+     * A value of -1 means that the height is unknown
      */
     public abstract int height { get; set; }
 
     /**
-     * The number of bits per pixel used to represent the video or image resource.
-     * A value of -1 means that the color depth is unknown and will not, or did not, appear in DIDL-Lite XML.
+     * The number of bits per pixel in the video or image resource (this.uri).
+     * A value of -1 means that the color depth is unknown
      */
     public abstract int color_depth { get; set; }
 
diff --git a/src/librygel-server/rygel-writable-container.vala b/src/librygel-server/rygel-writable-container.vala
index dc87a79..9229015 100644
--- a/src/librygel-server/rygel-writable-container.vala
+++ b/src/librygel-server/rygel-writable-container.vala
@@ -84,7 +84,7 @@ public interface Rygel.WritableContainer : MediaContainer {
      *
      * @return nothing.
      */
-    public async abstract void add_item (MediaItem    item,
+    public async abstract void add_item (MediaFileItem    item,
                                          Cancellable? cancellable) throws Error;
 
 
diff --git a/src/librygel-server/rygel-xbmc-hacks.vala b/src/librygel-server/rygel-xbmc-hacks.vala
index f028ace..0a57a60 100644
--- a/src/librygel-server/rygel-xbmc-hacks.vala
+++ b/src/librygel-server/rygel-xbmc-hacks.vala
@@ -33,11 +33,11 @@ internal class Rygel.XBMCHacks : ClientHacks {
     }
 
     public override void apply (MediaObject object) {
-        if (!(object is MediaItem)) {
+        if (!(object is MediaFileItem)) {
             return;
         }
 
-        var item = object as MediaItem;
+        var item = object as MediaFileItem;
 
         if (item.mime_type == "audio/mp4" ||
             item.mime_type == "audio/3gpp" ||
diff --git a/src/librygel-server/rygel-xbox-hacks.vala b/src/librygel-server/rygel-xbox-hacks.vala
index 9592d45..13ae89b 100644
--- a/src/librygel-server/rygel-xbox-hacks.vala
+++ b/src/librygel-server/rygel-xbox-hacks.vala
@@ -121,7 +121,11 @@ internal class Rygel.XBoxHacks : ClientHacks {
             return;
         }
 
-        var item = object as MediaItem;
+        if (! (object is MediaFileItem)) {
+            return;
+        }
+
+        var item = object as MediaFileItem;
 
         if (item.mime_type == "video/x-msvideo") {
             item.mime_type = "video/avi";
diff --git a/src/plugins/external/rygel-external-item-factory.vala b/src/plugins/external/rygel-external-item-factory.vala
index a4ed8b2..11cb019 100644
--- a/src/plugins/external/rygel-external-item-factory.vala
+++ b/src/plugins/external/rygel-external-item-factory.vala
@@ -29,14 +29,14 @@ using FreeDesktop;
  * Creates item for external plugins.
  */
 public class Rygel.External.ItemFactory {
-    public async MediaItem create (string                    id,
-                                   string                    type,
-                                   string                    title,
-                                   HashTable<string,Variant> props,
-                                   string                    service_name,
-                                   MediaContainer            parent)
-                                   throws IOError, DBusError {
-        MediaItem item;
+    public async MediaFileItem create (string                    id,
+                                       string                    type,
+                                       string                    title,
+                                       HashTable<string,Variant> props,
+                                       string                    service_name,
+                                       MediaContainer            parent)
+                                       throws IOError, DBusError {
+        MediaFileItem item;
 
         if (type.has_prefix ("music") ||
             type.has_prefix ("audio.music")) {
@@ -130,7 +130,7 @@ public class Rygel.External.ItemFactory {
         this.set_audio_metadata (video, props, service_name);
     }
 
-    private void set_generic_metadata (MediaItem                 item,
+    private void set_generic_metadata (MediaFileItem             item,
                                        HashTable<string,Variant> props,
                                        string                    service_name) {
         item.mime_type = get_mandatory_string_value (props,
diff --git a/src/plugins/gst-launch/rygel-gst-launch-root-container.vala b/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
index b917c91..d307c34 100644
--- a/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
+++ b/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
@@ -53,7 +53,7 @@ public class Rygel.GstLaunch.RootContainer : SimpleContainer {
             var launch_line = config.get_string (CONFIG_GROUP,
                                                  "%s-launch".printf (name));
             string dlna_profile = null;
-            MediaItem item;
+            MediaFileItem item;
             try {
                 dlna_profile = config.get_string (CONFIG_GROUP,
                                                   "%s-dlnaprofile".printf
