Sequencer Source Example Code
This topic shows example code for using the Sequencer Source in Microsoft Media Foundation. It contains the following sections.
- CPlaylist Class
- Creating an Instance of CPlaylist
- Adding and Removing Playlist Segments
- Handling Session Events
- Related topics
CPlaylist Class
The CPlaylist
class derives from the CPlayer
class that is shown in the tutorial How to Play Media Files with Media Foundation.
const DWORD MAX_PLAYLIST_SEGMENTS = 128;
// For this example, there is a hard-coded limit to the number of playlist
// segments, to avoid using a dynamically sized list.
class CPlaylist : public CPlayer
{
private:
IMFSequencerSource *m_pSequencerSource;
IMFPresentationClock *m_pPresentationClock;
MFTIME m_PresentationTimeOffset;
DWORD m_ActiveSegment;
LONGLONG m_hnsSegmentDuration;
// Table of segment IDs and topology IDs.
struct
{
MFSequencerElementId SegmentID;
TOPOID TopoID;
} m_segments[MAX_PLAYLIST_SEGMENTS];
DWORD m_count;
private:
MFSequencerElementId LastSegment() const
{
return m_segments[ m_count - 1].SegmentID;
}
HRESULT LookupTopoID(TOPOID id, DWORD *pIndex);
HRESULT OnSessionEvent(IMFMediaEvent *pEvent, MediaEventType meType);
HRESULT OnTopologyStatus(IMFMediaEvent *pEvent);
HRESULT OnNewPresentation(IMFMediaEvent *pEvent);
HRESULT AddSegment(PCWSTR pszURL);
HRESULT QueueNextSegment(IMFPresentationDescriptor *pPD);
CPlaylist(HWND hWnd, HRESULT *phr);
~CPlaylist();
HRESULT Initialize();
public:
static HRESULT CreateInstance(HWND hwnd, CPlaylist **ppPlayer);
HRESULT AddToPlaylist(PCWSTR pszURL);
HRESULT DeleteSegment(DWORD index);
HRESULT SkipTo(DWORD index);
DWORD NumSegments() { return m_count; }
DWORD ActiveSegment() const { return m_ActiveSegment; }
HRESULT GetPlaybackTime(MFTIME *phnsPresentation, MFTIME *phnsSegment);
LONGLONG SegmentDuration() { return m_hnsSegmentDuration; }
};
Creating an Instance of CPlaylist
The CPlaylist::CreateInstance
method creates a new CPlaylist
object. Internally, this method calls CPlaylist::Initialize
to initialize the object. The Initialize
method calls MFCreateSequencerSource to create the sequence source. It also calls IMFMediaSession::GetClock to get a pointer to the presentation clock.
/*static*/
HRESULT CPlaylist::CreateInstance(HWND hwnd, CPlaylist **ppPlayer)
{
*ppPlayer = NULL;
HRESULT hr = S_OK;
CPlaylist *pPlayer = new (std::nothrow) CPlaylist(hwnd, &hr);
if (pPlayer == NULL)
{
return E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
hr = pPlayer->Initialize();
}
if (SUCCEEDED(hr))
{
*ppPlayer = pPlayer;
}
else
{
pPlayer->Release();
}
return hr;
}
HRESULT CPlaylist::Initialize()
{
IMFClock *pClock = NULL;
HRESULT hr = CPlayer::CreateSession();
if (FAILED(hr))
{
goto done;
}
// Create an instance of the sequencer Source.
hr = MFCreateSequencerSource(NULL, &m_pSequencerSource);
if (FAILED(hr))
{
goto done;
}
hr = m_pSequencerSource->QueryInterface(IID_PPV_ARGS(&m_pSource));
if (FAILED(hr))
{
goto done;
}
// Get the presentation clock.
hr = m_pSession->GetClock(&pClock);
if (FAILED(hr))
{
goto done;
}
hr = pClock->QueryInterface(IID_PPV_ARGS(&m_pPresentationClock));
done:
SafeRelease(&pClock);
return hr;
}
CPlaylist::CPlaylist(HWND hWnd, HRESULT *phr)
: CPlayer(NULL, hWnd, phr),
m_pSequencerSource (NULL),
m_pPresentationClock (NULL),
m_PresentationTimeOffset (0),
m_ActiveSegment((DWORD)-1),
m_hnsSegmentDuration(0),
m_count(0)
{
}
CPlaylist::~CPlaylist()
{
SafeRelease(&m_pSequencerSource);
SafeRelease(&m_pPresentationClock);
}
Adding and Removing Playlist Segments
The AddSegment
method adds a new playlist segment.
This method performs the following steps:
- Creates a playback topology. The code for this step is shown in the topic Creating Playback Topologies.
- Calls IMFSequencerSource::AppendTopology to add the topology to the playlist.
- On the first segment, gets the value of the MF_PD_DURATION attribute, which contains the playback duration.
- Stores the segment ID and topology ID in a lookup table.
// Adds a segment to the sequencer.
HRESULT CPlaylist::AddSegment(PCWSTR pszURL)
{
IMFMediaSource *pMediaSource = NULL;
IMFPresentationDescriptor *pPD = NULL;
IMFTopology *pTopology = NULL;
MFSequencerElementId SegmentId;
TOPOID TopologyID = 0;
HRESULT hr = CreateMediaSource(pszURL, &pMediaSource);
if (FAILED(hr))
{
goto done;
}
hr = pMediaSource->CreatePresentationDescriptor(&pPD);
if (FAILED(hr))
{
goto done;
}
hr = CreatePlaybackTopology(pMediaSource, pPD, m_hwndVideo, &pTopology);
if (FAILED(hr))
{
goto done;
}
hr = m_pSequencerSource->AppendTopology(
pTopology,
SequencerTopologyFlags_Last,
&SegmentId
);
if (FAILED(hr))
{
goto done;
}
hr = pTopology->GetTopologyID(&TopologyID);
if (FAILED(hr))
{
goto done;
}
if (m_count == 0)
{
// Get the segment duration
m_hnsSegmentDuration = MFGetAttributeUINT64(pPD, MF_PD_DURATION, 0);
}
m_segments[m_count].SegmentID = SegmentId;
m_segments[m_count].TopoID = TopologyID;
m_count++;
done:
SafeRelease(&pMediaSource);
SafeRelease(&pTopology);
SafeRelease(&pPD);
return hr;
}
The AddSegment
method is private to CPlaylist
, and is called from the following public method:
// Adds a new segment to the playlist.
HRESULT CPlaylist::AddToPlaylist(PCWSTR pszURL)
{
if (NumSegments() >= MAX_PLAYLIST_SEGMENTS)
{
return MF_E_OUT_OF_RANGE;
}
HRESULT hr = S_OK;
IMFPresentationDescriptor *pPD = NULL;
BOOL bFirstSegment = (NumSegments() == 0);
if (!bFirstSegment)
{
// Remove the "last segment" flag from the last segment.
hr = m_pSequencerSource->UpdateTopologyFlags(LastSegment(), 0);
if (FAILED(hr))
{
goto done;
}
}
// Create the topology and add it to the sequencer.
hr = AddSegment(pszURL);
if (FAILED(hr))
{
goto done;
}
// If this is the first segment, queue it on the session.
if (bFirstSegment)
{
hr = m_pSource->CreatePresentationDescriptor(&pPD);
if (FAILED(hr))
{
goto done;
}
hr = QueueNextSegment(pPD);
if (FAILED(hr))
{
goto done;
}
}
if (m_state < Started)
{
m_state = OpenPending;
}
done:
SafeRelease(&pPD);
return hr;
}
When you create the first playlist segment, you must queue the segment topology on the Media Session, as follows:
- Query the sequencer source for the IMFMediaSourceTopologyProvider interface.
- Pass the presentation descriptor to the IMFMediaSourceTopologyProvider::GetMediaSourceTopology method. This method gets a pointer to the segment topology. Note that this topology is not exactly the same as the playback topology that you created earliers. Instead, it is a modified version of that topology. For more information, see About the Sequencer Source.
- Queue the topology on the Media Session by calling IMFMediaSession::SetTopology.
The following code shows these steps. This same code is also used when the playlist prerolls the next segment.
// Queues the next topology on the session.
HRESULT CPlaylist::QueueNextSegment(IMFPresentationDescriptor *pPD)
{
IMFMediaSourceTopologyProvider *pTopoProvider = NULL;
IMFTopology *pTopology = NULL;
//Get the topology for the presentation descriptor
HRESULT hr = m_pSequencerSource->QueryInterface(IID_PPV_ARGS(&pTopoProvider));
if (FAILED(hr))
{
goto done;
}
hr = pTopoProvider->GetMediaSourceTopology(pPD, &pTopology);
if (FAILED(hr))
{
goto done;
}
//Set the topology on the media session
m_pSession->SetTopology(NULL, pTopology);
done:
SafeRelease(&pTopoProvider);
SafeRelease(&pTopology);
return hr;
}
To delete a playlist segment, call IMFSequencerSource::DeleteTopology. The segment is specified by segment ID. (This is why the application should cache the list of segment IDs.)
// Deletes the corresponding topology from the sequencer source
HRESULT CPlaylist::DeleteSegment(DWORD index)
{
if (index >= m_count)
{
return E_INVALIDARG;
}
if (index == m_ActiveSegment)
{
return E_INVALIDARG;
}
MFSequencerElementId LastSegId = LastSegment();
MFSequencerElementId SegmentID = m_segments[index].SegmentID;
HRESULT hr = m_pSequencerSource->DeleteTopology( SegmentID );
if (FAILED(hr))
{
goto done;
}
//Delete the segment entry from the list.
// Move everything up one slot.
for (DWORD i = index; i < m_count - 1; i++)
{
m_segments[i] = m_segments[i + 1];
}
m_count --;
// Is the deleted topology the last one?
if (LastSegId == SegmentID)
{
//Get the new last segment id
LastSegId = LastSegment();
//set this topology as the last in the sequencer
hr = m_pSequencerSource->UpdateTopologyFlags(LastSegId, SequencerTopologyFlags_Last);
}
done:
return hr;
}
Handling Session Events
HRESULT CPlaylist::OnTopologyStatus(IMFMediaEvent *pEvent)
{
IMFTopology *pTopology = NULL;
UINT value = 0;
DWORD SegmentIndex;
TOPOID TopoID;
HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &value);
if (FAILED(hr))
{
goto done;
}
switch(value)
{
case MF_TOPOSTATUS_STARTED_SOURCE:
// Get information about the new segment
hr = GetEventObject(pEvent, &pTopology);
if (FAILED(hr))
{
hr = S_OK;
goto done;
}
hr = pTopology->GetTopologyID(&TopoID);
if (FAILED(hr))
{
goto done;
}
hr = LookupTopoID(TopoID, &SegmentIndex);
if (FAILED(hr))
{
goto done;
}
m_ActiveSegment = SegmentIndex;
hr = GetDurationFromTopology(pTopology, &m_hnsSegmentDuration);
break;
case MF_TOPOSTATUS_ENDED:
m_ActiveSegment = (DWORD)-1;
break;
case MF_TOPOSTATUS_READY:
if (m_state == OpenPending)
{
m_state = Stopped;
}
break;
}
done:
SafeRelease(&pTopology);
return hr;
}
HRESULT CPlaylist::LookupTopoID(TOPOID id, DWORD *pIndex)
{
DWORD index;
for (index = 0; index < m_count; index++)
{
if (m_segments[ index ].TopoID == id)
{
break;
}
}
if (index == m_count)
{
return MF_E_NOT_FOUND;
}
*pIndex = index;
return S_OK;
}
HRESULT CPlaylist::OnSessionEvent(IMFMediaEvent *pEvent, MediaEventType meType)
{
if (meType == MESessionNotifyPresentationTime)
{
m_PresentationTimeOffset =
MFGetAttributeUINT64(pEvent, MF_EVENT_PRESENTATION_TIME_OFFSET, 0);
}
return S_OK;
}
HRESULT CPlaylist::OnNewPresentation(IMFMediaEvent *pEvent)
{
IMFPresentationDescriptor *pPD = NULL;
HRESULT hr = GetEventObject(pEvent, &pPD);
if (SUCCEEDED(hr))
{
// Queue the next segment on the media session
hr = QueueNextSegment(pPD);
}
SafeRelease(&pPD);
return hr;
}
// Skips to the specified segment in the sequencer source
HRESULT CPlaylist::SkipTo(DWORD index)
{
if (index >= m_count)
{
return E_INVALIDARG;
}
MFSequencerElementId ID = m_segments[index].SegmentID;
PROPVARIANT var;
HRESULT hr = MFCreateSequencerSegmentOffset(ID, NULL, &var);
if (SUCCEEDED(hr))
{
hr = m_pSession->Start(&MF_TIME_FORMAT_SEGMENT_OFFSET, &var);
PropVariantClear(&var);
}
return hr;
}
HRESULT GetDurationFromTopology(IMFTopology *pTopology, LONGLONG *phnsDuration)
{
*phnsDuration = 0;
IMFCollection *pSourceNodes = NULL;
IMFTopologyNode *pNode = NULL;
IMFPresentationDescriptor *pPD = NULL;
HRESULT hr = pTopology->GetSourceNodeCollection(&pSourceNodes);
if (FAILED(hr))
{
goto done;
}
hr = GetCollectionObject(pSourceNodes, 0, &pNode);
if (FAILED(hr))
{
goto done;
}
hr = pNode->GetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, IID_PPV_ARGS(&pPD));
if (FAILED(hr))
{
goto done;
}
*phnsDuration = MFGetAttributeUINT64(pPD, MF_PD_DURATION, 0);
done:
SafeRelease(&pSourceNodes);
SafeRelease(&pNode);
SafeRelease(&pPD);
return hr;
}
HRESULT CPlaylist::GetPlaybackTime(
MFTIME *phnsPresentation, // Relative to start of presentation.
MFTIME *phnsSegment // Relative to start of segment.
)
{
*phnsPresentation = 0;
*phnsSegment = 0;
HRESULT hr = m_pPresentationClock->GetTime(phnsPresentation);
if (SUCCEEDED(hr))
{
*phnsSegment = (*phnsPresentation) - m_PresentationTimeOffset;
}
return hr;
}
Related topics