--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -46,32 +46,35 @@ class ErrorResult;
class MediaResource;
class MediaDecoder;
class VideoFrameContainer;
namespace dom {
class MediaKeys;
class TextTrack;
class TimeRanges;
class WakeLock;
+class MediaTrack;
}
}
class nsITimer;
class nsRange;
class nsIRunnable;
namespace mozilla {
namespace dom {
// Number of milliseconds between timeupdate events as defined by spec
#define TIMEUPDATE_MS 250
class MediaError;
class MediaSource;
class TextTrackList;
+class AudioTrackList;
+class VideoTrackList;
class HTMLMediaElement : public nsGenericHTMLElement,
public nsIObserver,
public MediaDecoderOwner,
public nsIAudioChannelAgentCallback
{
public:
typedef mozilla::TimeStamp TimeStamp;
@@ -277,16 +280,18 @@ public:
void NotifyAddedSource();
/**
* Called when there's been an error fetching the resource. This decides
* whether it's appropriate to fire an error event.
*/
void NotifyLoadError();
+ void NotifyMediaTrackEnabled(MediaTrack* aTrack);
+
virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
/**
* Returns the current load ID. Asynchronous events store the ID that was
* current when they were enqueued, and if it has changed when they come to
* fire, they consider themselves cancelled, and don't fire.
*/
uint32_t GetCurrentLoadID() { return mCurrentLoadID; }
@@ -556,16 +561,20 @@ public:
AudioChannel MozAudioChannelType() const
{
return mAudioChannel;
}
void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
+ AudioTrackList* AudioTracks();
+
+ VideoTrackList* VideoTracks();
+
TextTrackList* TextTracks();
already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
const nsAString& aLabel,
const nsAString& aLanguage);
void AddTextTrack(TextTrack* aTextTrack) {
GetOrCreateTextTrackManager()->AddTextTrack(aTextTrack);
@@ -1099,17 +1108,18 @@ protected:
// Playback of the video is paused either due to calling the
// 'Pause' method, or playback not yet having started.
WakeLockBoolWrapper mPaused;
enum MutedReasons {
MUTED_BY_CONTENT = 0x01,
MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
- MUTED_BY_AUDIO_CHANNEL = 0x04
+ MUTED_BY_AUDIO_CHANNEL = 0x04,
+ MUTED_BY_AUDIO_TRACK = 0x08
};
uint32_t mMuted;
// True if the media statistics are currently being shown by the builtin
// video controls
bool mStatsShowing;
@@ -1203,20 +1213,29 @@ protected:
AudioChannel mAudioChannel;
// The audio channel has been faded.
bool mAudioChannelFaded;
// Is this media element playing?
bool mPlayingThroughTheAudioChannel;
+ // Disable the video playback by track selection. This flag might not be
+ // enough if we ever expand the ability of supporting multi-tracks video
+ // playback.
+ bool mDisableVideo;
+
// An agent used to join audio channel service.
nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
nsRefPtr<TextTrackManager> mTextTrackManager;
+ nsRefPtr<AudioTrackList> mAudioTrackList;
+
+ nsRefPtr<VideoTrackList> mVideoTrackList;
+
MediaWaitingFor mWaitingFor;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_HTMLMediaElement_h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -71,16 +71,20 @@
#include "MediaMetadataManager.h"
#include "MediaSourceDecoder.h"
#include "AudioChannelService.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/WakeLock.h"
+#include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/AudioTrackList.h"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
#include "mozilla/dom/TextTrack.h"
#include "ImageContainer.h"
#include "nsRange.h"
#include <algorithm>
#ifdef PR_LOGGING
static PRLogModuleInfo* gMediaElementLog;
@@ -420,16 +424,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
if (tmp->mSrcStream) {
// Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
// gets unhooked correctly.
tmp->EndSrcMediaStreamPlayback();
@@ -441,16 +447,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
@@ -608,16 +616,23 @@ void HTMLMediaElement::AbortExistingLoad
mLoadWaitStatus = NOT_WAITING;
// Set a new load ID. This will cause events which were enqueued
// with a different load ID to silently be cancelled.
mCurrentLoadID++;
bool fireTimeUpdate = false;
+ // When aborting the existing loads, empty the objects in audio track list and
+ // video track list, no events (in particular, no removetrack events) are
+ // fired as part of this. Ending MediaStream sends track ended notifications,
+ // so we empty the track lists prior.
+ AudioTracks()->EmptyTracks();
+ VideoTracks()->EmptyTracks();
+
if (mDecoder) {
fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
ShutdownDecoder();
}
if (mSrcStream) {
EndSrcMediaStreamPlayback();
}
if (mMediaSource) {
@@ -853,16 +868,34 @@ void HTMLMediaElement::NotifyLoadError()
} else if (mSourceLoadCandidate) {
DispatchAsyncSourceError(mSourceLoadCandidate);
QueueLoadFromSourceTask();
} else {
NS_WARNING("Should know the source we were loading from!");
}
}
+void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
+{
+ if (!aTrack) {
+ return;
+ }
+
+ // TODO: We are dealing with single audio track and video track for now.
+ if (AudioTrack* track = aTrack->AsAudioTrack()) {
+ if (!track->Enabled()) {
+ SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
+ } else {
+ SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
+ }
+ } else if (VideoTrack* track = aTrack->AsVideoTrack()) {
+ mDisableVideo = !track->Selected();
+ }
+}
+
void HTMLMediaElement::LoadFromSourceChildren()
{
NS_ASSERTION(mDelayingLoadEvent,
"Should delay load event (if in document) during load");
NS_ASSERTION(mIsLoadingFromSourceChildren,
"Must remember we're loading from source children");
nsIDocument* parentDoc = OwnerDoc()->GetParentDocument();
@@ -2007,16 +2040,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
mShuttingDown(false),
mSuspendedForPreloadNone(false),
mMediaSecurityVerified(false),
mCORSMode(CORS_NONE),
mHasAudio(false),
mDownloadSuspendedByCache(false),
mAudioChannelFaded(false),
mPlayingThroughTheAudioChannel(false),
+ mDisableVideo(false),
mWaitingFor(MediaWaitingFor::None)
{
#ifdef PR_LOGGING
if (!gMediaElementLog) {
gMediaElementLog = PR_NewLogModule("nsMediaElement");
}
if (!gMediaElementEventsLog) {
gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
@@ -2782,16 +2816,19 @@ void HTMLMediaElement::SetupSrcMediaStre
}
ChangeDelayLoadStatus(false);
GetSrcMediaStream()->AddAudioOutput(this);
GetSrcMediaStream()->SetAudioOutputVolume(this, float(mMuted ? 0.0 : mVolume));
VideoFrameContainer* container = GetVideoFrameContainer();
if (container) {
GetSrcMediaStream()->AddVideoOutput(container);
}
+
+ mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
+
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
AddRemoveSelfReference();
// FirstFrameLoaded(false) will be called when the stream has current data,
// to complete the setup by entering the HAVE_CURRENT_DATA state.
@@ -3969,16 +4006,36 @@ HTMLMediaElement::IsEventAttributeName(n
}
NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
{
SetVolumeInternal();
return NS_OK;
}
+AudioTrackList*
+HTMLMediaElement::AudioTracks()
+{
+ if (!mAudioTrackList) {
+ nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+ mAudioTrackList = new AudioTrackList(window, this);
+ }
+ return mAudioTrackList;
+}
+
+VideoTrackList*
+HTMLMediaElement::VideoTracks()
+{
+ if (!mVideoTrackList) {
+ nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+ mVideoTrackList = new VideoTrackList(window, this);
+ }
+ return mVideoTrackList;
+}
+
/* readonly attribute TextTrackList textTracks; */
TextTrackList*
HTMLMediaElement::TextTracks()
{
return GetOrCreateTextTrackManager()->TextTracks();
}
already_AddRefed<TextTrack>
--- a/content/html/content/src/HTMLVideoElement.cpp
+++ b/content/html/content/src/HTMLVideoElement.cpp
@@ -74,16 +74,20 @@ HTMLVideoElement::~HTMLVideoElement()
}
nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size)
{
if (mMediaSize.width == -1 && mMediaSize.height == -1) {
return NS_ERROR_FAILURE;
}
+ if (mDisableVideo) {
+ return NS_ERROR_FAILURE;
+ }
+
size->height = mMediaSize.height;
size->width = mMediaSize.width;
return NS_OK;
}
bool
HTMLVideoElement::ParseAttribute(int32_t aNamespaceID,
nsIAtom* aAttribute,
--- a/content/media/AudioTrack.cpp
+++ b/content/media/AudioTrack.cpp
@@ -45,13 +45,18 @@ AudioTrack::SetEnabledInternal(bool aEna
// If this AudioTrack is no longer in its original AudioTrackList, then
// whether it is enabled or not has no effect on its original list.
if (!mList) {
return;
}
if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
mList->CreateAndDispatchChangeEvent();
+
+ HTMLMediaElement* element = mList->GetMediaElement();
+ if (element) {
+ element->NotifyMediaTrackEnabled(this);
+ }
}
}
} // namespace dom
} //namespace mozilla
--- a/content/media/AudioTrackList.h
+++ b/content/media/AudioTrackList.h
@@ -24,14 +24,17 @@ public:
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
AudioTrack* operator[](uint32_t aIndex);
// WebIDL
AudioTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
AudioTrack* GetTrackById(const nsAString& aId);
+
+protected:
+ virtual AudioTrackList* AsAudioTrackList() MOZ_OVERRIDE { return this; }
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_AudioTrackList_h
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -3,16 +3,20 @@
* 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/. */
#include "DOMMediaStream.h"
#include "nsContentUtils.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/LocalMediaStreamBinding.h"
#include "mozilla/dom/AudioNode.h"
+#include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/AudioTrackList.h"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
#include "MediaStreamGraph.h"
#include "AudioStreamTrack.h"
#include "VideoStreamTrack.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream)
@@ -79,21 +83,23 @@ public:
DOMMediaStream* stream = mListener->GetStream();
if (!stream) {
return NS_OK;
}
nsRefPtr<MediaStreamTrack> track;
if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) {
track = stream->CreateDOMTrack(mID, mType);
+ stream->NotifyMediaStreamTrackCreated(track);
} else {
track = stream->GetDOMTrackFor(mID);
}
if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) {
track->NotifyEnded();
+ stream->NotifyMediaStreamTrackEnded(track);
}
return NS_OK;
}
StreamTime mEndTime;
nsRefPtr<StreamListener> mListener;
TrackID mID;
uint32_t mEvents;
@@ -370,16 +376,101 @@ DOMMediaStream::CheckTracksAvailable()
// Some expected tracks not available yet. Try this callback again later.
*mRunOnTracksAvailable.AppendElement() = callbacks[i].forget();
continue;
}
cb->NotifyTracksAvailable(this);
}
}
+already_AddRefed<AudioTrack>
+DOMMediaStream::CreateAudioTrack(AudioStreamTrack* aStreamTrack)
+{
+ nsAutoString id;
+ nsAutoString label;
+ aStreamTrack->GetId(id);
+ aStreamTrack->GetLabel(label);
+
+ return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
+ label, EmptyString(),
+ aStreamTrack->Enabled());
+}
+
+already_AddRefed<VideoTrack>
+DOMMediaStream::CreateVideoTrack(VideoStreamTrack* aStreamTrack)
+{
+ nsAutoString id;
+ nsAutoString label;
+ aStreamTrack->GetId(id);
+ aStreamTrack->GetLabel(label);
+
+ return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
+ label, EmptyString());
+}
+
+void
+DOMMediaStream::ConstructMediaTracks(AudioTrackList* aAudioTrackList,
+ VideoTrackList* aVideoTrackList)
+{
+ if (mHintContents & DOMMediaStream::HINT_CONTENTS_AUDIO) {
+ MediaTrackListListener listener(aAudioTrackList);
+ mMediaTrackListListeners.AppendElement(listener);
+ }
+ if (mHintContents & DOMMediaStream::HINT_CONTENTS_VIDEO) {
+ MediaTrackListListener listener(aVideoTrackList);
+ mMediaTrackListListeners.AppendElement(listener);
+ }
+
+ int firstEnabledVideo = -1;
+ for (uint32_t i = 0; i < mTracks.Length(); ++i) {
+ if (AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack()) {
+ nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
+ aAudioTrackList->AddTrack(track);
+ } else if (VideoStreamTrack* t = mTracks[i]->AsVideoStreamTrack()) {
+ nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
+ aVideoTrackList->AddTrack(track);
+ firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
+ ? (aVideoTrackList->Length() - 1)
+ : firstEnabledVideo;
+ }
+ }
+
+ if (aVideoTrackList->Length() > 0) {
+ // If media resource does not indicate a particular set of video tracks to
+ // enable, the one that is listed first in the element's videoTracks object
+ // must be selected.
+ int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
+ (*aVideoTrackList)[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
+ }
+}
+
+void
+DOMMediaStream::NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack)
+{
+ for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
+ if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
+ nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
+ mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
+ } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
+ nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
+ mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
+ }
+ }
+}
+
+void
+DOMMediaStream::NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack)
+{
+ nsAutoString id;
+ aTrack->GetId(id);
+ for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
+ mMediaTrackListListeners[i].NotifyMediaTrackEnded(id);
+ }
+}
+
DOMLocalMediaStream::~DOMLocalMediaStream()
{
if (mStream) {
// Make sure Listeners of this stream know it's going away
Stop();
}
}
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -32,30 +32,40 @@ namespace mozilla {
class MediaStream;
namespace dom {
class AudioNode;
class MediaStreamTrack;
class AudioStreamTrack;
class VideoStreamTrack;
+class AudioTrack;
+class VideoTrack;
+class AudioTrackList;
+class VideoTrackList;
+class MediaTrackListListener;
}
class MediaStreamDirectListener;
/**
* DOM wrapper for MediaStreams.
*/
class DOMMediaStream : public nsIDOMMediaStream,
public nsWrapperCache
{
friend class DOMLocalMediaStream;
typedef dom::MediaStreamTrack MediaStreamTrack;
typedef dom::AudioStreamTrack AudioStreamTrack;
typedef dom::VideoStreamTrack VideoStreamTrack;
+ typedef dom::AudioTrack AudioTrack;
+ typedef dom::VideoTrack VideoTrack;
+ typedef dom::AudioTrackList AudioTrackList;
+ typedef dom::VideoTrackList VideoTrackList;
+ typedef dom::MediaTrackListListener MediaTrackListListener;
public:
typedef uint8_t TrackTypeHints;
DOMMediaStream();
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMMediaStream)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -202,23 +212,37 @@ public:
*/
void AddConsumerToKeepAlive(nsISupports* aConsumer)
{
if (!IsFinished() && !mNotifiedOfMediaStreamGraphShutdown) {
mConsumersToKeepAlive.AppendElement(aConsumer);
}
}
+ /**
+ * If loading and playing a MediaStream in a media element, for each
+ * MediaStreamTrack in the MediaStream, create a corresponding AudioTrack or
+ * VideoTrack during the phase of resource fetching.
+ */
+ void ConstructMediaTracks(AudioTrackList* aAudioTrackList,
+ VideoTrackList* aVideoTrackList);
+
+ void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
+
+ void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
+
protected:
virtual ~DOMMediaStream();
void Destroy();
void InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
void InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
void InitStreamCommon(MediaStream* aStream);
+ already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
+ already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
void CheckTracksAvailable();
class StreamListener;
friend class StreamListener;
// StreamTime at which the currentTime attribute would return 0.
StreamTime mLogicalStreamStartTime;
@@ -239,16 +263,20 @@ protected:
nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
// Indicate what track types we eventually expect to add to this stream
uint8_t mHintContents;
// Indicate what track types have been added to this stream
uint8_t mTrackTypesAvailable;
bool mNotifiedOfMediaStreamGraphShutdown;
+ // Send notifications to AudioTrackList or VideoTrackList, if this MediaStream
+ // is consumed by a HTMLMediaElement.
+ nsTArray<MediaTrackListListener> mMediaTrackListListeners;
+
private:
void NotifyPrincipalChanged();
// Principal identifying who may access the contents of this stream.
// If null, this stream can be used by anyone because it has no content yet.
nsCOMPtr<nsIPrincipal> mPrincipal;
nsTArray<PrincipalChangeObserver*> mPrincipalChangeObservers;
// this is used in gUM and WebRTC to identify peers that this stream
--- a/content/media/MediaTrackList.cpp
+++ b/content/media/MediaTrackList.cpp
@@ -2,24 +2,52 @@
/* vim:set ts=2 sw=2 et tw=78: */
/* 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/. */
#include "MediaTrack.h"
#include "MediaTrackList.h"
#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/TrackEvent.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
+void
+MediaTrackListListener::NotifyMediaTrackCreated(MediaTrack* aTrack)
+{
+ if (!mMediaTrackList && !aTrack) {
+ return;
+ }
+
+ if (aTrack->AsAudioTrack() && mMediaTrackList->AsAudioTrackList()) {
+ mMediaTrackList->AddTrack(aTrack);
+ } else if (aTrack->AsVideoTrack() && mMediaTrackList->AsVideoTrackList()) {
+ mMediaTrackList->AddTrack(aTrack);
+ }
+}
+
+void
+MediaTrackListListener::NotifyMediaTrackEnded(const nsAString& aId)
+{
+ if (!mMediaTrackList) {
+ return;
+ }
+
+ const nsRefPtr<MediaTrack> track = mMediaTrackList->GetTrackById(aId);
+ if (track) {
+ mMediaTrackList->RemoveTrack(track);
+ }
+}
+
MediaTrackList::MediaTrackList(nsPIDOMWindow* aOwnerWindow,
HTMLMediaElement* aMediaElement)
: DOMEventTargetHelper(aOwnerWindow)
, mMediaElement(aMediaElement)
{
}
MediaTrackList::~MediaTrackList()
@@ -72,16 +100,38 @@ MediaTrackList::AddTrack(MediaTrack* aTr
void
MediaTrackList::RemoveTrack(const nsRefPtr<MediaTrack>& aTrack)
{
mTracks.RemoveElement(aTrack);
aTrack->SetTrackList(nullptr);
CreateAndDispatchTrackEventRunner(aTrack, NS_LITERAL_STRING("removetrack"));
}
+already_AddRefed<AudioTrack>
+MediaTrackList::CreateAudioTrack(const nsAString& aId,
+ const nsAString& aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage,
+ bool aEnabled)
+{
+ nsRefPtr<AudioTrack> track = new AudioTrack(aId, aKind, aLabel, aLanguage,
+ aEnabled);
+ return track.forget();
+}
+
+already_AddRefed<VideoTrack>
+MediaTrackList::CreateVideoTrack(const nsAString& aId,
+ const nsAString& aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage)
+{
+ nsRefPtr<VideoTrack> track = new VideoTrack(aId, aKind, aLabel, aLanguage);
+ return track.forget();
+}
+
void
MediaTrackList::EmptyTracks()
{
for (uint32_t i = 0; i < mTracks.Length(); ++i) {
mTracks[i]->SetTrackList(nullptr);
}
mTracks.Clear();
}
--- a/content/media/MediaTrackList.h
+++ b/content/media/MediaTrackList.h
@@ -11,16 +11,51 @@
namespace mozilla {
namespace dom {
class HTMLMediaElement;
class MediaTrack;
class AudioTrackList;
class VideoTrackList;
+class AudioTrack;
+class VideoTrack;
+class MediaTrackList;
+
+/**
+ * This is for the media resource to notify its audio track and video track,
+ * when a media-resource-specific track has ended, or whether it has enabled or
+ * not. All notification methods are called from the main thread.
+ */
+class MediaTrackListListener
+{
+public:
+ MediaTrackListListener(MediaTrackList* aMediaTrackList)
+ : mMediaTrackList(aMediaTrackList) {};
+
+ ~MediaTrackListListener()
+ {
+ mMediaTrackList = nullptr;
+ };
+
+ // Notify mMediaTrackList that a track has created by the media resource,
+ // and this corresponding MediaTrack object should be added into
+ // mMediaTrackList, and fires a addtrack event.
+ void NotifyMediaTrackCreated(MediaTrack* aTrack);
+
+ // Notify mMediaTrackList that a track has ended by the media resource,
+ // and this corresponding MediaTrack object should be removed from
+ // mMediaTrackList, and fires a removetrack event.
+ void NotifyMediaTrackEnded(const nsAString& aId);
+
+protected:
+ // A weak reference to a MediaTrackList object, its lifetime managed by its
+ // owner.
+ MediaTrackList* mMediaTrackList;
+};
/**
* Base class of AudioTrackList and VideoTrackList. The AudioTrackList and
* VideoTrackList objects represent a dynamic list of zero or more audio and
* video tracks respectively.
*
* When a media element is to forget its media-resource-specific tracks, its
* audio track list and video track list will be emptied.
@@ -39,16 +74,29 @@ public:
// The return value is non-null, assert an error if aIndex is out of bound
// for array mTracks.
MediaTrack* operator[](uint32_t aIndex);
void AddTrack(MediaTrack* aTrack);
void RemoveTrack(const nsRefPtr<MediaTrack>& aTrack);
+ static already_AddRefed<AudioTrack>
+ CreateAudioTrack(const nsAString& aId,
+ const nsAString& aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage,
+ bool aEnabled);
+
+ static already_AddRefed<VideoTrack>
+ CreateVideoTrack(const nsAString& aId,
+ const nsAString& aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage);
+
virtual void EmptyTracks();
void CreateAndDispatchChangeEvent();
// WebIDL
MediaTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
MediaTrack* GetTrackById(const nsAString& aId);
@@ -57,20 +105,30 @@ public:
{
return mTracks.Length();
}
IMPL_EVENT_HANDLER(change)
IMPL_EVENT_HANDLER(addtrack)
IMPL_EVENT_HANDLER(removetrack)
+ friend class MediaTrackListListener;
+ friend class AudioTrack;
+ friend class VideoTrack;
+
protected:
void CreateAndDispatchTrackEventRunner(MediaTrack* aTrack,
const nsAString& aEventName);
+ virtual AudioTrackList* AsAudioTrackList() { return nullptr; }
+
+ virtual VideoTrackList* AsVideoTrackList() { return nullptr; }
+
+ HTMLMediaElement* GetMediaElement() { return mMediaElement; }
+
nsTArray<nsRefPtr<MediaTrack>> mTracks;
nsRefPtr<HTMLMediaElement> mMediaElement;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_MediaTrackList_h
--- a/content/media/VideoTrack.cpp
+++ b/content/media/VideoTrack.cpp
@@ -67,13 +67,18 @@ VideoTrack::SetEnabledInternal(bool aEna
} else {
list.mSelectedIndex = -1;
}
// Fire the change event at selection changes on this video track, shall
// propose a spec change later.
if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
list.CreateAndDispatchChangeEvent();
+
+ HTMLMediaElement* element = mList->GetMediaElement();
+ if (element) {
+ element->NotifyMediaTrackEnabled(this);
+ }
}
}
} // namespace dom
} //namespace mozilla
--- a/content/media/VideoTrackList.h
+++ b/content/media/VideoTrackList.h
@@ -36,16 +36,19 @@ public:
}
VideoTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
VideoTrack* GetTrackById(const nsAString& aId);
friend class VideoTrack;
+protected:
+ virtual VideoTrackList* AsVideoTrackList() MOZ_OVERRIDE { return this; }
+
private:
int32_t mSelectedIndex;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_VideoTrackList_h
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -374,16 +374,17 @@ skip-if = buildapp == 'b2g' || toolkit =
[test_mediarecorder_record_nosrc.html]
[test_mediarecorder_record_session.html]
[test_mediarecorder_record_startstopstart.html]
[test_mediarecorder_record_stopms.html]
[test_mediarecorder_record_timeslice.html]
[test_mediarecorder_reload_crash.html]
[test_mediarecorder_unsupported_src.html]
[test_mediarecorder_record_getdata_afterstart.html]
+[test_mediatrack_events_and_consuming_ms.html]
[test_metadata.html]
[test_mixed_principals.html]
skip-if = true # bug 567954 and intermittent leaks
[test_mozHasAudio.html]
[test_networkState.html]
[test_new_audio.html]
[test_no_load_event.html]
[test_paused.html]
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_mediatrack_events_and_consuming_ms.html
@@ -0,0 +1,189 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test track interfaces when consuming a MediaStream from gUM</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function startTest() {
+ navigator.mozGetUserMedia({audio:true, video:true, fake:true},
+ function(stream) {
+ var audioStreamTracks = stream.getAudioTracks();
+ var videoStreamTracks = stream.getVideoTracks();
+
+ var audioOnchange = 0;
+ var audioOnaddtrack = 0;
+ var audioOnremovetrack = 0;
+ var videoOnchange = 0;
+ var videoOnaddtrack = 0;
+ var videoOnremovetrack = 0;
+
+ var element = document.createElement("video");
+ isnot(element.audioTracks, undefined, "HTMLMediaElement::AudioTracks() property should be available.");
+ isnot(element.videoTracks, undefined, "HTMLMediaElement::VideoTracks() property should be available.");
+
+ function verify_event(e, type) {
+ is(e.type, type, "Event type should be " + type);
+ ok(e.isTrusted, "Event should be trusted.");
+ ok(!e.bubbles, "Event shouldn't bubble.");
+ ok(!e.cancelable, "Event shouldn't be cancelable.");
+ }
+
+ function setAudioEnabled(enabled, index) {
+ return new Promise(function(resolve, reject) {
+ element.audioTracks[index].enabled = enabled;
+ element.audioTracks.onchange = function(e) {
+ ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+ ok(true, 'onchange is expected to be called from audioTracks.');
+ verify_event(e, "change");
+ audioOnchange++;
+ resolve();
+ }
+ });
+ }
+
+ function setVideoSelected(selected, index) {
+ return new Promise(function(resolve, reject) {
+ element.videoTracks[index].selected = selected;
+ element.videoTracks.onchange = function(e) {
+ ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+ ok(true, 'onchange is expected to be called from videoTracks.');
+ verify_event(e, "change");
+
+ var noVideoSelected = true;
+ for (var i=0; i < element.videoTracks.length; ++i) {
+ var track = element.videoTracks[i];
+ if (track.selected == true) {
+ noVideoSelected = false;
+ break;
+ }
+ }
+
+ if (selected) {
+ is(element.videoTracks.selectedIndex, index,
+ 'SelectedIndex shuld be '+index+' if video track is set selected.');
+ } else {
+ if (noVideoSelected) {
+ is(element.videoTracks.selectedIndex, -1,
+ 'SelectedIndex shuld be -1 if no video track is set selected.');
+ } else {
+ reject();
+ }
+ }
+ videoOnchange++;
+ resolve();
+ }
+ });
+ }
+
+ element.audioTracks.onaddtrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+ ok(true, 'onaddtrack is expected to be called from audioTracks.');
+ verify_event(e, "addtrack");
+ audioOnaddtrack++;
+ }
+
+ element.audioTracks.onremovetrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+ ok(true, 'onremovetrack is expected to be called from audioTracks.');
+ verify_event(e, "removetrack");
+ audioOnremovetrack++;
+ }
+
+ element.videoTracks.onaddtrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+ ok(true, 'onaddtrack is expected to be called from videoTracks.');
+ verify_event(e, "addtrack");
+ videoOnaddtrack++;
+ }
+
+ element.videoTracks.onremovetrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+ ok(true, 'onremovetrack is expected to be called from videoTracks.');
+ verify_event(e, "removetrack");
+ videoOnremovetrack++;
+ }
+
+
+ element.onended = function() {
+ is(audioOnchange, 2, 'change event on audioTracks should fired twice.');
+ is(videoOnchange, 2, 'change event on videoTracks should fired twice.');
+
+ is(audioOnremovetrack, audioStreamTracks.length,
+ 'Calls of onremovetrack from audioTracks should match the numbers of AudioStreamTrack.');
+ is(videoOnremovetrack, videoStreamTracks.length,
+ 'Calls of onremovetrack from videoTracks should match the numbers of VideoStreamTrack.');
+
+ SimpleTest.finish();
+ }
+
+ var promise = new Promise(function(resolve, reject) {
+ element.mozSrcObject = stream;
+ element.play();
+
+ element.onloadedmetadata = function() {
+ is(audioOnaddtrack, audioStreamTracks.length,
+ 'Calls of onaddtrack from audioTracks should match the numbers of AudioStreamTrack.');
+ is(videoOnaddtrack, videoStreamTracks.length,
+ 'Calls of onaddtrack from videoTracks should match the numbers of VideoStreamTrack.');
+
+ is(element.audioTracks.length, audioStreamTracks.length,
+ 'Length of audioTracks should be the same as the length of AudioStreamTrack.');
+ is(element.videoTracks.length, videoStreamTracks.length,
+ 'Length of videoTracks should be the same as the length of VideoStreamTrack.');
+
+ for (var i=0; i < audioStreamTracks.length; ++i) {
+ var track = element.audioTracks.getTrackById(audioStreamTracks[i].id);
+ isnot(track, null, 'Successfully get '+ track.id + ' from audioTracks.');
+ }
+ for (var i=0; i < videoStreamTracks.length; ++i) {
+ var track = element.videoTracks.getTrackById(videoStreamTracks[i].id);
+ isnot(track, null, 'Successfully get '+ track.id + ' from videoTracks.');
+ }
+
+ is(element.videoTracks.selectedIndex, 0,
+ 'The first video track is set selected as default.');
+
+ resolve();
+ }
+ });
+
+ promise.then(function() {
+ var p1 = setAudioEnabled(false, 0);
+ var p2 = setVideoSelected(false, 0);
+ return Promise.all([p1, p2]);
+ }).catch(function(err) {
+ ok(false, 'Something went wrong in onchange callback.');
+ }).then(function() {
+ var p3 = setAudioEnabled(true, 0);
+ var p4 = setVideoSelected(true, 0);
+ return Promise.all([p3, p4]);
+ }).catch(function(err) {
+ ok(false, 'Something went wrong in onchange callback.');
+ }).then(function() {
+ stream.stop();
+ });
+ },
+ function(err) {
+ ok(false, 'Unexpected error fired with: ' + err);
+ SimpleTest.finish();
+ }
+ );
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -81,18 +81,20 @@ interface HTMLMediaElement : HTMLElement
[SetterThrows]
attribute double volume;
attribute boolean muted;
[SetterThrows]
attribute boolean defaultMuted;
// TODO: Bug 847379
// tracks
- //readonly attribute AudioTrackList audioTracks;
- //readonly attribute VideoTrackList videoTracks;
+ [Pref="media.track.enabled"]
+ readonly attribute AudioTrackList audioTracks;
+ [Pref="media.track.enabled"]
+ readonly attribute VideoTrackList videoTracks;
[Pref="media.webvtt.enabled"]
readonly attribute TextTrackList textTracks;
[Pref="media.webvtt.enabled"]
TextTrack addTextTrack(TextTrackKind kind,
optional DOMString label = "",
optional DOMString language = "");
};