Hi bubbleguum,
I am delighted that you have been "motivated" to address the challenge of gapless playback :-)
It has been a bit challenging to implement especially for the control point part, due to having to find a hacky way to detect track changes to issue successive SetNextAVTransportURI commands.
What I did is abusing GetPositionInfo().TrackURI to detect track changes as explained in one of the previous post.
Strictly speaking GetPositionInfo().TrackURI is NOT the right solution. UPnP has two types of URI, namely "media URI" and "track URI". A media URI refers to a container having one or more tracks in it, and a track URI refers to any one such track within the media container. If the media URI is the URI of a single music track (e.g.
http://something.mp3) then the media and track URIs are deemed identical (i.e. the media container contains just one track). HOWEVER if the media URI is the URI of a playlist (e.g.
http://something.m3u) then the media and track URIs are NOT identical; rather the track URI would be the URI of one of the music tracks within the m3u playlist.
The current and next media URI (called AVTransportUri & NextAvTransportUri) are written by the property setter actions SetAVTransportUri & SetNextAVTransportUri, and they are read by the property getter action GetMediaInfo. The track URI is read by the getter action GetPositionInfo; the track URI has no setter action.
In your case, you are setting single track URIs via SetAVTransportUri & SetNextAVTransportUri, and --
in this special case -- GetPositionInfo will return the same URI as GetMediaInfo. However in the general case you cannot rely on GetPositionInfo and GetMediaInfo returning the same URIs.
For example if you set a playlist containing (say) 3 tracks via SetAVTransportUri, and another playlist also containing (say) 3 tracks via SetNextAvTransportUri, then the player will play those six tracks in sequence (and BTW nominally gaplesslessly). GetPositionInfo.TrackUri will transition through six consecutive URIs (corresponding to each of the six tracks), whereas GetMediaInfo will transition through only two URIs (corresponding to the URIs set by SetAvTransportUri & SetNextAvTransportUri).
If you use the GetPositionInfo.TrackUri transition to trigger sending the next SetNextAvTransportUri, you will in general not be playing the playlists that the user had intended. Instead you should use GetMediaInfo transitions to trigger sending the next SetNextAvTransportUri (either check if GetMediaInfo.CurrentTrackUri has transitioned to what you had set with SetNextAvTransportUri, or check if GetMediaInfo.NextTrackUri has transitioned to a now empty value).
If you insist on using GetPositionInfo.TrackUri then you must explicitly check the actual value of TrackUri and determine if the track is WITHIN one of the playlists (in which case don't trigger the next SetNext) or if it represents the specific transition from the last track in the first playlist to the first track in the second playlist (in which case DO trigger the next SetNext)...
Note that when the renderer does NOT support SetNext, you must also follow a similar logic to decide when to send the next SetAvTransportUri i.e. you should only send the next SetAvTransportUri when GetMediaInfo.CurrentUri transitions to an empty value, (or when GetPositionInfo.TrackUri transitions to an empty value), or when the player state transitions to STOPPED (the latter is perhaps the easiest choice in this case).
For the Control Point to consider gapless control, the renderer must have action SetNextAVTransportURI.
As you can see from the above, this statement is not 100% true. If you use only SetAVTransportURI and pass playlists, then all transitions WITHIN playlists can (depending on the renderer) be played gaplessly. But all transitions BETWEEN playlists will necessarily have a gap.