Transferring playlists through WPD

A playlist (.WPL, .M3U, etc.) is a text file which contains a list of filenames. This list is the "playlist". So transferring the playlist should be easy right? Well, yes it is - if you just want to transfer it as a text file...

However if you want the playlist semantics to be sent to the device as well, you need to go an extra step (well, several extra steps :). Let's take a look.

A .WPL playlist in WPD is an object of format WPD_OBJECT_FORMAT_WPLPLAYLIST. Similar WPD formats exist for .M3U, .MPL, .ASX, etc. To associate tracks with the playlist, you must make use of the WPD_OBJECT_REFERENCES parameter. The WPD_OBJECT_REFERENCES parameter is available for most container-style formats. It is of type IPortableDevicePropVariantCollection.

Now even though WPD_OBJECT_REFERENCES is of type IPortableDevicePropVariantCollection, it can only legally hold PROPVARIANTs of a specific vartype i.e. VT_LPWSTR. Each string must correspond to a valid WPD object ID - these will be the object IDs that you want to associate with the playlist.

Assume we have the following objects already on the device.

 Store
 |
 |-- o1 (Happy.wma) [WPD_OBJECT_FORMAT_WMA]
 |-- o2 (Sad.wma)   [WPD_OBJECT_FORMAT_WMA]

 

Next we want to add a playlist to the device. So we send the playlist first as a regular object of format WPLPLAYLIST.

 Store
 |
 |-- o1 (Happy.wma) 
 |-- o2 (Sad.wma)
 |-- o3 (Emotions.wpl) [WPD_OBJECT_FORMAT_WPLPLAYLIST]

 

Now that we have a WPLPLAYLIST object (given by handle o3), we can set the WPD_OBJECT_REFERENCES property on it. Let's say we want to include o1 and o2 in the playlist.

 LPWSTR pwszPlaylistObject = NULL;
if (hr == S_OK)
{
    // IMP: Add your code to transfer an object to the device and get back
    // a WPD object handle. We are assigning o3 just for this example
    pwszPlaylistObject = L"o3";
}

CComPtr<IPortableDeviceProperties> spProperties;
if (hr == S_OK)
{
    // IMP: Add your code to obtain the Properties interface from the
    // Device interface
}

CComPtr<IPortableDeviceValues> spValues;
CComPtr<IPortableDevicePropVariantCollection> spReferences;

if (hr == S_OK)
{
    // IMP: Add your code to CoCreate spValues and spReferences here
}

// Add o1 and o2 for the references 0 - your code will probably have
// a list of object IDs that you obtained prior by enumeration
PROPVARIANT pv = {0};
if (hr == S_OK)    
{
    pv.vt = VT_LPWSTR;
    pv.pwszVal = L"o1";
    
    hr = spReferences->Add(&pv);
}

if (hr == S_OK)    
{
    pv.vt = VT_LPWSTR;
    pv.pwszVal = L"o2";
    
    hr = spReferences->Add(&pv);
}

// Add the references to the values collection to send to the device
if (hr == S_OK)
{
    hr = spValues->SetIPortableDevicePropVariantCollectionValue(
                        WPD_OBJECT_REFERENCES, spReferences);
}

// Set the references property on the Playlist object
CComPtr<IPortableDeviceValues> spResults;
if (hr == S_OK)
{
    hr = spProperties->SetValues(pwszPlaylistObject, spValues, &spResults);
}

Briefly - you need to create an instance of a PropVariantCollection and add the object IDs that need to be part of the playlist to that collection. Next add that PropVariantCollection to the Values bag that we need to send down to the device. Be sure to specify the REFERENCES propertykey. Finally, use the SetValues API call to send the new value to the device.

Note that:

  • Your objects need to already exist on the device before they can be added to the playlist. So if you are syncing a playlist, sync the referenced content first, get the content object IDs and then sync the playlist object and set the REFERENCES property.
  • Modifying the REFERENCES property will clobber any previous value. If you need to add a new object ID, read the property first, add to the collection and then set the property back.
  • You can also set the references at the same time as playlist object creation. The CreateObject* APIs allow property values to be specified in the same call.

As a curiosity, when you transfer the playlist object to the device, you can transfer just a 0-sized object using CreateObjectWithPropertiesOnly and skip the data since it isn't going to be processed by the device anyway.