зеркало из https://github.com/mozilla/gecko-dev.git
Bug 744896 - Part 2: Enable track interfaces for media elements that are consuming a MediaStream. r=bz,roc
This commit is contained in:
Родитель
003338ee2c
Коммит
6c1db2d620
|
@ -51,6 +51,7 @@ class MediaKeys;
|
|||
class TextTrack;
|
||||
class TimeRanges;
|
||||
class WakeLock;
|
||||
class MediaTrack;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +68,8 @@ namespace dom {
|
|||
class MediaError;
|
||||
class MediaSource;
|
||||
class TextTrackList;
|
||||
class AudioTrackList;
|
||||
class VideoTrackList;
|
||||
|
||||
class HTMLMediaElement : public nsGenericHTMLElement,
|
||||
public nsIObserver,
|
||||
|
@ -282,6 +285,8 @@ public:
|
|||
*/
|
||||
void NotifyLoadError();
|
||||
|
||||
void NotifyMediaTrackEnabled(MediaTrack* aTrack);
|
||||
|
||||
virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
|
||||
|
||||
/**
|
||||
|
@ -561,6 +566,10 @@ public:
|
|||
|
||||
void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
|
||||
|
||||
AudioTrackList* AudioTracks();
|
||||
|
||||
VideoTrackList* VideoTracks();
|
||||
|
||||
TextTrackList* TextTracks();
|
||||
|
||||
already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
|
||||
|
@ -1104,7 +1113,8 @@ protected:
|
|||
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;
|
||||
|
@ -1208,11 +1218,20 @@ protected:
|
|||
// 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -76,6 +76,10 @@
|
|||
#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"
|
||||
|
@ -425,6 +429,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
|
|||
}
|
||||
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
|
||||
|
||||
|
@ -446,6 +452,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
|
|||
}
|
||||
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
|
||||
|
||||
|
@ -613,6 +621,13 @@ void HTMLMediaElement::AbortExistingLoads()
|
|||
|
||||
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();
|
||||
|
@ -858,6 +873,24 @@ void HTMLMediaElement::NotifyLoadError()
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -2012,6 +2045,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
|||
mDownloadSuspendedByCache(false),
|
||||
mAudioChannelFaded(false),
|
||||
mPlayingThroughTheAudioChannel(false),
|
||||
mDisableVideo(false),
|
||||
mWaitingFor(MediaWaitingFor::None)
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
|
@ -2787,6 +2821,9 @@ void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
|
|||
if (container) {
|
||||
GetSrcMediaStream()->AddVideoOutput(container);
|
||||
}
|
||||
|
||||
mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
|
||||
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
|
||||
|
@ -3974,6 +4011,26 @@ NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
|
|||
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()
|
||||
|
|
|
@ -79,6 +79,10 @@ nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (mDisableVideo) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
size->height = mMediaSize.height;
|
||||
size->width = mMediaSize.width;
|
||||
return NS_OK;
|
||||
|
|
|
@ -50,6 +50,11 @@ AudioTrack::SetEnabledInternal(bool aEnabled, int aFlags)
|
|||
|
||||
if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
|
||||
mList->CreateAndDispatchChangeEvent();
|
||||
|
||||
HTMLMediaElement* element = mList->GetMediaElement();
|
||||
if (element) {
|
||||
element->NotifyMediaTrackEnabled(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ public:
|
|||
AudioTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
|
||||
|
||||
AudioTrack* GetTrackById(const nsAString& aId);
|
||||
|
||||
protected:
|
||||
virtual AudioTrackList* AsAudioTrackList() MOZ_OVERRIDE { return this; }
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
#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"
|
||||
|
@ -84,11 +88,13 @@ public:
|
|||
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;
|
||||
}
|
||||
|
@ -375,6 +381,91 @@ DOMMediaStream::CheckTracksAvailable()
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -37,6 +37,11 @@ class AudioNode;
|
|||
class MediaStreamTrack;
|
||||
class AudioStreamTrack;
|
||||
class VideoStreamTrack;
|
||||
class AudioTrack;
|
||||
class VideoTrack;
|
||||
class AudioTrackList;
|
||||
class VideoTrackList;
|
||||
class MediaTrackListListener;
|
||||
}
|
||||
|
||||
class MediaStreamDirectListener;
|
||||
|
@ -51,6 +56,11 @@ class DOMMediaStream : public nsIDOMMediaStream,
|
|||
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;
|
||||
|
@ -207,6 +217,18 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
|
@ -214,6 +236,8 @@ protected:
|
|||
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();
|
||||
|
||||
|
@ -244,6 +268,10 @@ protected:
|
|||
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();
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#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"
|
||||
|
@ -15,6 +16,33 @@
|
|||
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)
|
||||
|
@ -77,6 +105,28 @@ MediaTrackList::RemoveTrack(const nsRefPtr<MediaTrack>& aTrack)
|
|||
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()
|
||||
{
|
||||
|
|
|
@ -16,6 +16,41 @@ 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
|
||||
|
@ -44,6 +79,19 @@ public:
|
|||
|
||||
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();
|
||||
|
@ -62,10 +110,20 @@ public:
|
|||
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;
|
||||
};
|
||||
|
|
|
@ -72,6 +72,11 @@ VideoTrack::SetEnabledInternal(bool aEnabled, int aFlags)
|
|||
// propose a spec change later.
|
||||
if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
|
||||
list.CreateAndDispatchChangeEvent();
|
||||
|
||||
HTMLMediaElement* element = mList->GetMediaElement();
|
||||
if (element) {
|
||||
element->NotifyMediaTrackEnabled(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ public:
|
|||
|
||||
friend class VideoTrack;
|
||||
|
||||
protected:
|
||||
virtual VideoTrackList* AsVideoTrackList() MOZ_OVERRIDE { return this; }
|
||||
|
||||
private:
|
||||
int32_t mSelectedIndex;
|
||||
};
|
||||
|
|
|
@ -380,6 +380,7 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' # mimetype check, bug 969289
|
|||
[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
|
||||
|
|
|
@ -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>
|
|
@ -86,8 +86,10 @@ interface HTMLMediaElement : HTMLElement {
|
|||
|
||||
// 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"]
|
||||
|
|
Загрузка…
Ссылка в новой задаче