summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/test/FakeMediaStreams.h
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling/test/FakeMediaStreams.h')
-rw-r--r--media/webrtc/signaling/test/FakeMediaStreams.h656
1 files changed, 656 insertions, 0 deletions
diff --git a/media/webrtc/signaling/test/FakeMediaStreams.h b/media/webrtc/signaling/test/FakeMediaStreams.h
new file mode 100644
index 000000000..117d26905
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -0,0 +1,656 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FAKE_MEDIA_STREAM_H_
+#define FAKE_MEDIA_STREAM_H_
+
+#include <set>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include "nsNetCID.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsISupportsImpl.h"
+#include "nsServiceManagerUtils.h"
+
+// #includes from MediaStream.h
+#include "mozilla/Mutex.h"
+#include "AudioSegment.h"
+#include "MediaSegment.h"
+#include "StreamTracks.h"
+#include "VideoSegment.h"
+#include "nsTArray.h"
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+ class MediaStreamGraphImpl;
+ class MediaSegment;
+ class PeerConnectionImpl;
+ class PeerConnectionMedia;
+ class RemoteSourceStreamInfo;
+};
+
+
+namespace mozilla {
+
+class MediaStreamGraph;
+
+static MediaStreamGraph* gGraph;
+
+struct AudioChannel {
+ enum {
+ Normal
+ };
+};
+
+enum MediaStreamGraphEvent : uint32_t;
+enum TrackEventCommand : uint32_t;
+
+class MediaStreamGraph {
+public:
+ // Keep this in sync with the enum in MediaStreamGraph.h
+ enum GraphDriverType {
+ AUDIO_THREAD_DRIVER,
+ SYSTEM_THREAD_DRIVER,
+ OFFLINE_THREAD_DRIVER
+ };
+ static MediaStreamGraph* GetInstance(GraphDriverType aDriverType,
+ uint32_t aType) {
+ if (gGraph) {
+ return gGraph;
+ }
+ gGraph = new MediaStreamGraph();
+ return gGraph;
+ }
+};
+}
+
+class Fake_VideoSink {
+public:
+ Fake_VideoSink() {}
+ virtual void SegmentReady(mozilla::MediaSegment* aSegment) = 0;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_VideoSink)
+protected:
+ virtual ~Fake_VideoSink() {}
+};
+
+class Fake_MediaStream;
+class Fake_SourceMediaStream;
+
+class Fake_MediaStreamListener
+{
+protected:
+ virtual ~Fake_MediaStreamListener() {}
+
+public:
+ virtual void NotifyQueuedTrackChanges(mozilla::MediaStreamGraph* aGraph, mozilla::TrackID aID,
+ mozilla::StreamTime aTrackOffset,
+ mozilla::TrackEventCommand aTrackEvents,
+ const mozilla::MediaSegment& aQueuedMedia,
+ Fake_MediaStream* aInputStream,
+ mozilla::TrackID aInputTrackID) {}
+ virtual void NotifyPull(mozilla::MediaStreamGraph* aGraph, mozilla::StreamTime aDesiredTime) = 0;
+ virtual void NotifyQueuedAudioData(mozilla::MediaStreamGraph* aGraph, mozilla::TrackID aID,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::AudioSegment& aQueuedMedia,
+ Fake_MediaStream* aInputStream,
+ mozilla::TrackID aInputTrackID) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamListener)
+};
+
+class Fake_DirectMediaStreamListener : public Fake_MediaStreamListener
+{
+protected:
+ virtual ~Fake_DirectMediaStreamListener() {}
+
+public:
+ virtual void NotifyRealtimeData(mozilla::MediaStreamGraph* graph, mozilla::TrackID tid,
+ mozilla::StreamTime offset,
+ const mozilla::MediaSegment& media) = 0;
+};
+
+class Fake_MediaStreamTrackListener
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackListener)
+
+protected:
+ virtual ~Fake_MediaStreamTrackListener() {}
+
+public:
+ virtual void NotifyQueuedChanges(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aQueuedMedia) = 0;
+};
+
+class Fake_DirectMediaStreamTrackListener : public Fake_MediaStreamTrackListener
+{
+protected:
+ virtual ~Fake_DirectMediaStreamTrackListener() {}
+
+public:
+ virtual void NotifyRealtimeTrackData(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aMedia) = 0;
+ enum class InstallationResult {
+ STREAM_NOT_SUPPORTED,
+ SUCCESS
+ };
+ virtual void NotifyDirectListenerInstalled(InstallationResult aResult) = 0;
+ virtual void NotifyDirectListenerUninstalled() = 0;
+};
+
+class Fake_MediaStreamVideoSink : public Fake_DirectMediaStreamTrackListener{
+public:
+ Fake_MediaStreamVideoSink() {}
+
+ void NotifyQueuedChanges(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aQueuedMedia) override {}
+
+ void NotifyRealtimeTrackData(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aMedia) override {}
+ void NotifyDirectListenerInstalled(InstallationResult aResult) override {}
+ void NotifyDirectListenerUninstalled() override {}
+
+ virtual void SetCurrentFrames(const mozilla::VideoSegment& aSegment) {};
+ virtual void ClearFrames() {};
+
+protected:
+ virtual ~Fake_MediaStreamVideoSink() {}
+};
+
+// Note: only one listener supported
+class Fake_MediaStream {
+ protected:
+ virtual ~Fake_MediaStream() { Stop(); }
+
+ struct BoundTrackListener
+ {
+ BoundTrackListener(Fake_MediaStreamTrackListener* aListener,
+ mozilla::TrackID aTrackID)
+ : mListener(aListener), mTrackID(aTrackID) {}
+ RefPtr<Fake_MediaStreamTrackListener> mListener;
+ mozilla::TrackID mTrackID;
+ };
+
+ public:
+ Fake_MediaStream () : mListeners(), mTrackListeners(), mMutex("Fake MediaStream") {}
+
+ static uint32_t GraphRate() { return 16000; }
+
+ void AddListener(Fake_MediaStreamListener *aListener) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mListeners.insert(aListener);
+ }
+
+ void RemoveListener(Fake_MediaStreamListener *aListener) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mListeners.erase(aListener);
+ }
+
+ void AddTrackListener(Fake_MediaStreamTrackListener *aListener,
+ mozilla::TrackID aTrackID) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mTrackListeners.push_back(BoundTrackListener(aListener, aTrackID));
+ }
+
+ void RemoveTrackListener(Fake_MediaStreamTrackListener *aListener,
+ mozilla::TrackID aTrackID) {
+ mozilla::MutexAutoLock lock(mMutex);
+ for (std::vector<BoundTrackListener>::iterator it = mTrackListeners.begin();
+ it != mTrackListeners.end(); ++it) {
+ if (it->mListener == aListener && it->mTrackID == aTrackID) {
+ mTrackListeners.erase(it);
+ return;
+ }
+ }
+ }
+
+ void NotifyPull(mozilla::MediaStreamGraph* graph,
+ mozilla::StreamTime aDesiredTime) {
+
+ mozilla::MutexAutoLock lock(mMutex);
+ std::set<RefPtr<Fake_MediaStreamListener>>::iterator it;
+ for (it = mListeners.begin(); it != mListeners.end(); ++it) {
+ (*it)->NotifyPull(graph, aDesiredTime);
+ }
+ }
+
+ virtual Fake_SourceMediaStream *AsSourceStream() { return nullptr; }
+
+ virtual mozilla::MediaStreamGraphImpl *GraphImpl() { return nullptr; }
+ virtual nsresult Start() { return NS_OK; }
+ virtual nsresult Stop() { return NS_OK; }
+ virtual void StopStream() {}
+
+ virtual void Periodic() {}
+
+ double StreamTimeToSeconds(mozilla::StreamTime aTime);
+ mozilla::StreamTime
+ TicksToTimeRoundDown(mozilla::TrackRate aRate, mozilla::TrackTicks aTicks);
+ mozilla::TrackTicks TimeToTicksRoundUp(mozilla::TrackRate aRate,
+ mozilla::StreamTime aTime);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStream);
+
+ protected:
+ std::set<RefPtr<Fake_MediaStreamListener>> mListeners;
+ std::vector<BoundTrackListener> mTrackListeners;
+ mozilla::Mutex mMutex; // Lock to prevent the listener list from being modified while
+ // executing Periodic().
+};
+
+class Fake_MediaPeriodic : public nsITimerCallback {
+public:
+ explicit Fake_MediaPeriodic(Fake_MediaStream *aStream) : mStream(aStream),
+ mCount(0) {}
+ void Detach() {
+ mStream = nullptr;
+ }
+
+ int GetTimesCalled() { return mCount; }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+protected:
+ virtual ~Fake_MediaPeriodic() {}
+
+ Fake_MediaStream *mStream;
+ int mCount;
+};
+
+
+class Fake_SourceMediaStream : public Fake_MediaStream {
+ public:
+ Fake_SourceMediaStream() : mSegmentsAdded(0),
+ mDesiredTime(0),
+ mPullEnabled(false),
+ mStop(false),
+ mPeriodic(new Fake_MediaPeriodic(this)) {}
+
+ enum {
+ ADDTRACK_QUEUED = 0x01 // Queue track add until FinishAddTracks()
+ };
+
+ void AddVideoSink(const RefPtr<Fake_VideoSink>& aSink) {
+ mSink = aSink;
+ }
+
+ void AddTrack(mozilla::TrackID aID, mozilla::StreamTime aStart,
+ mozilla::MediaSegment* aSegment, uint32_t aFlags = 0) {
+ delete aSegment;
+ }
+ void AddAudioTrack(mozilla::TrackID aID, mozilla::TrackRate aRate, mozilla::StreamTime aStart,
+ mozilla::AudioSegment* aSegment, uint32_t aFlags = 0) {
+ delete aSegment;
+ }
+ void FinishAddTracks() {}
+ void EndTrack(mozilla::TrackID aID) {}
+
+ bool AppendToTrack(mozilla::TrackID aID, mozilla::MediaSegment* aSegment,
+ mozilla::MediaSegment *aRawSegment) {
+ return AppendToTrack(aID, aSegment);
+ }
+
+ bool AppendToTrack(mozilla::TrackID aID, mozilla::MediaSegment* aSegment) {
+ bool nonZeroSample = false;
+ MOZ_ASSERT(aSegment);
+ if(aSegment->GetType() == mozilla::MediaSegment::AUDIO) {
+ //On audio segment append, we verify for validity
+ //of the audio samples.
+ mozilla::AudioSegment* audio =
+ static_cast<mozilla::AudioSegment*>(aSegment);
+ mozilla::AudioSegment::ChunkIterator iter(*audio);
+ while(!iter.IsEnded()) {
+ mozilla::AudioChunk& chunk = *(iter);
+ MOZ_ASSERT(chunk.mBuffer);
+ const int16_t* buf =
+ static_cast<const int16_t*>(chunk.mChannelData[0]);
+ for(int i=0; i<chunk.mDuration; i++) {
+ if(buf[i]) {
+ //atleast one non-zero sample found.
+ nonZeroSample = true;
+ break;
+ }
+ }
+ //process next chunk
+ iter.Next();
+ }
+ if(nonZeroSample) {
+ //we increment segments count if
+ //atleast one non-zero samples was found.
+ ++mSegmentsAdded;
+ }
+ } else {
+ //in the case of video segment appended, we just increase the
+ //segment count.
+ if (mSink.get()) {
+ mSink->SegmentReady(aSegment);
+ }
+ ++mSegmentsAdded;
+ }
+ return true;
+ }
+
+ void AdvanceKnownTracksTime(mozilla::StreamTime aKnownTime) {}
+
+ void SetPullEnabled(bool aEnabled) {
+ mPullEnabled = aEnabled;
+ }
+ void AddDirectListener(Fake_MediaStreamListener* aListener) {}
+ void RemoveDirectListener(Fake_MediaStreamListener* aListener) {}
+
+ //Don't pull anymore data,if mStop is true.
+ virtual void StopStream() {
+ mStop = true;
+ }
+
+ virtual Fake_SourceMediaStream *AsSourceStream() { return this; }
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+
+ virtual void Periodic();
+
+ virtual int GetSegmentsAdded() {
+ return mSegmentsAdded;
+ }
+
+ protected:
+ int mSegmentsAdded;
+ uint64_t mDesiredTime;
+ bool mPullEnabled;
+ bool mStop;
+ RefPtr<Fake_MediaPeriodic> mPeriodic;
+ RefPtr<Fake_VideoSink> mSink;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+class Fake_DOMMediaStream;
+
+class Fake_MediaStreamTrackSource
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackSource)
+
+protected:
+ virtual ~Fake_MediaStreamTrackSource() {}
+};
+
+class Fake_MediaStreamTrack
+{
+ friend class mozilla::PeerConnectionImpl;
+ friend class mozilla::PeerConnectionMedia;
+ friend class mozilla::RemoteSourceStreamInfo;
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrack)
+
+ Fake_MediaStreamTrack(bool aIsVideo, Fake_DOMMediaStream* aOwningStream) :
+ mIsVideo (aIsVideo),
+ mOwningStream (aOwningStream),
+ mTrackID(mIsVideo ? 1 : 0)
+ {
+ static size_t counter = 0;
+ std::ostringstream os;
+ os << counter++;
+ mID = os.str();
+ }
+
+ std::string GetId() const { return mID; }
+ void AssignId(const std::string& id) { mID = id; }
+ mozilla::MediaStreamGraphImpl* GraphImpl() { return nullptr; }
+ const Fake_MediaStreamTrack* AsVideoStreamTrack() const
+ {
+ return mIsVideo? this : nullptr;
+ }
+ const Fake_MediaStreamTrack* AsAudioStreamTrack() const
+ {
+ return mIsVideo? nullptr : this;
+ }
+ uint32_t typeSize () const
+ {
+ return sizeof(Fake_MediaStreamTrack);
+ }
+ const char* typeName () const
+ {
+ return "Fake_MediaStreamTrack";
+ }
+ void AddListener(Fake_MediaStreamTrackListener *aListener);
+ void RemoveListener(Fake_MediaStreamTrackListener *aListener);
+ void AddDirectListener(Fake_DirectMediaStreamTrackListener *aListener)
+ {
+ AddListener(aListener);
+ aListener->NotifyDirectListenerInstalled(
+ Fake_DirectMediaStreamTrackListener::InstallationResult::STREAM_NOT_SUPPORTED);
+ }
+ void RemoveDirectListener(Fake_DirectMediaStreamTrackListener *aListener)
+ {
+ RemoveListener(aListener);
+ }
+
+ class PrincipalChangeObserver
+ {
+ public:
+ virtual void PrincipalChanged(Fake_MediaStreamTrack* aMediaStreamTrack) = 0;
+ };
+ void AddPrincipalChangeObserver(void* ignoredObserver) {}
+ void RemovePrincipalChangeObserver(void* ignoredObserver) {}
+
+private:
+ ~Fake_MediaStreamTrack() {}
+
+ const bool mIsVideo;
+ Fake_DOMMediaStream* mOwningStream;
+ mozilla::TrackID mTrackID;
+ std::string mID;
+};
+
+class Fake_DOMMediaStream : public nsISupports
+{
+ friend class mozilla::PeerConnectionMedia;
+protected:
+ virtual ~Fake_DOMMediaStream() {
+ // Note: memory leak
+ mMediaStream->Stop();
+ }
+
+public:
+ explicit Fake_DOMMediaStream(Fake_MediaStream *stream = nullptr)
+ : mMediaStream(stream ? stream : new Fake_MediaStream())
+ , mVideoTrack(new Fake_MediaStreamTrack(true, this))
+ , mAudioTrack(new Fake_MediaStreamTrack(false, this))
+ {
+ static size_t counter = 0;
+ std::ostringstream os;
+ os << counter++;
+ mID = os.str();
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static already_AddRefed<Fake_DOMMediaStream>
+ CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
+ mozilla::MediaStreamGraph* aGraph,
+ uint32_t aHintContents = 0) {
+ Fake_SourceMediaStream *source = new Fake_SourceMediaStream();
+
+ RefPtr<Fake_DOMMediaStream> ds = new Fake_DOMMediaStream(source);
+ ds->SetHintContents(aHintContents);
+
+ return ds.forget();
+ }
+
+ virtual void Stop() {} // Really DOMLocalMediaStream
+
+ virtual bool AddDirectListener(Fake_MediaStreamListener *aListener) { return false; }
+ virtual void RemoveDirectListener(Fake_MediaStreamListener *aListener) {}
+
+ Fake_MediaStream *GetInputStream() { return mMediaStream; }
+ Fake_MediaStream *GetOwnedStream() { return mMediaStream; }
+ Fake_MediaStream *GetPlaybackStream() { return mMediaStream; }
+ Fake_MediaStream *GetStream() { return mMediaStream; }
+ std::string GetId() const { return mID; }
+ void AssignId(const std::string& id) { mID = id; }
+ Fake_MediaStreamTrack* GetTrackById(const std::string& aId)
+ {
+ if (mHintContents & HINT_CONTENTS_AUDIO) {
+ if (mAudioTrack && mAudioTrack->GetId() == aId) {
+ return mAudioTrack;
+ }
+ }
+ if (mHintContents & HINT_CONTENTS_VIDEO) {
+ if (mVideoTrack && mVideoTrack->GetId() == aId) {
+ return mVideoTrack;
+ }
+ }
+ return nullptr;
+ }
+ Fake_MediaStreamTrack* GetOwnedTrackById(const std::string& aId)
+ {
+ return GetTrackById(aId);
+ }
+
+ // Hints to tell the SDP generator about whether this
+ // MediaStream probably has audio and/or video
+ typedef uint8_t TrackTypeHints;
+ enum {
+ HINT_CONTENTS_AUDIO = 0x01,
+ HINT_CONTENTS_VIDEO = 0x02
+ };
+ uint32_t GetHintContents() const { return mHintContents; }
+ void SetHintContents(uint32_t aHintContents) { mHintContents = aHintContents; }
+
+ void
+ GetTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ GetAudioTracks(aTracks);
+ GetVideoTracks(aTracks);
+ }
+
+ void GetAudioTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ if (mHintContents & HINT_CONTENTS_AUDIO) {
+ aTracks.AppendElement(mAudioTrack);
+ }
+ }
+
+ void
+ GetVideoTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ if (mHintContents & HINT_CONTENTS_VIDEO) {
+ aTracks.AppendElement(mVideoTrack);
+ }
+ }
+
+ bool
+ HasTrack(const Fake_MediaStreamTrack& aTrack) const
+ {
+ return ((mHintContents & HINT_CONTENTS_AUDIO) && aTrack.AsAudioStreamTrack()) ||
+ ((mHintContents & HINT_CONTENTS_VIDEO) && aTrack.AsVideoStreamTrack());
+ }
+
+ bool
+ OwnsTrack(const Fake_MediaStreamTrack& aTrack) const
+ {
+ return HasTrack(aTrack);
+ }
+
+ void SetTrackEnabled(mozilla::TrackID aTrackID, bool aEnabled) {}
+
+ void AddTrackInternal(Fake_MediaStreamTrack* aTrack) {}
+
+ already_AddRefed<Fake_MediaStreamTrack>
+ CreateDOMTrack(mozilla::TrackID aTrackID, mozilla::MediaSegment::Type aType,
+ Fake_MediaStreamTrackSource* aSource)
+ {
+ switch(aType) {
+ case mozilla::MediaSegment::AUDIO: {
+ return do_AddRef(mAudioTrack);
+ }
+ case mozilla::MediaSegment::VIDEO: {
+ return do_AddRef(mVideoTrack);
+ }
+ default: {
+ MOZ_CRASH("Unkown media type");
+ }
+ }
+ }
+
+private:
+ RefPtr<Fake_MediaStream> mMediaStream;
+
+ // tells the SDP generator about whether this
+ // MediaStream probably has audio and/or video
+ uint32_t mHintContents;
+ RefPtr<Fake_MediaStreamTrack> mVideoTrack;
+ RefPtr<Fake_MediaStreamTrack> mAudioTrack;
+
+ std::string mID;
+};
+
+class Fake_MediaStreamBase : public Fake_MediaStream {
+ public:
+ Fake_MediaStreamBase() : mPeriodic(new Fake_MediaPeriodic(this)) {}
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+
+ virtual int GetSegmentsAdded() {
+ return mPeriodic->GetTimesCalled();
+ }
+
+ private:
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<Fake_MediaPeriodic> mPeriodic;
+};
+
+
+class Fake_AudioStreamSource : public Fake_MediaStreamBase {
+ public:
+ Fake_AudioStreamSource() : Fake_MediaStreamBase(),
+ mCount(0),
+ mStop(false) {}
+ //Signaling Agent indicates us to stop generating
+ //further audio.
+ void StopStream() {
+ mStop = true;
+ }
+ virtual void Periodic();
+ int mCount;
+ bool mStop;
+};
+
+class Fake_VideoStreamSource : public Fake_MediaStreamBase {
+ public:
+ Fake_VideoStreamSource() : Fake_MediaStreamBase() {}
+};
+
+
+namespace mozilla {
+typedef Fake_MediaStream MediaStream;
+typedef Fake_SourceMediaStream SourceMediaStream;
+typedef Fake_MediaStreamListener MediaStreamListener;
+typedef Fake_DirectMediaStreamListener DirectMediaStreamListener;
+typedef Fake_MediaStreamTrackListener MediaStreamTrackListener;
+typedef Fake_DirectMediaStreamTrackListener DirectMediaStreamTrackListener;
+typedef Fake_DOMMediaStream DOMMediaStream;
+typedef Fake_DOMMediaStream DOMLocalMediaStream;
+typedef Fake_MediaStreamVideoSink MediaStreamVideoSink;
+
+namespace dom {
+typedef Fake_MediaStreamTrack MediaStreamTrack;
+typedef Fake_MediaStreamTrackSource MediaStreamTrackSource;
+}
+}
+
+#endif