import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,36 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_ASYNCEVENTRUNNER_H_
#define MOZILLA_ASYNCEVENTRUNNER_H_
#include "nsThreadUtils.h"
namespace mozilla {
template <typename T>
class AsyncEventRunner : public Runnable
{
public:
AsyncEventRunner(T* aTarget, const char* aName)
: mTarget(aTarget)
, mName(aName)
{}
NS_IMETHOD Run() override
{
mTarget->DispatchSimpleEvent(mName);
return NS_OK;
}
private:
RefPtr<T> mTarget;
const char* mName;
};
} // namespace mozilla
#endif /* MOZILLA_ASYNCEVENTRUNNER_H_ */

View file

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_AUTOTASKQUEUE_H_
#define MOZILLA_AUTOTASKQUEUE_H_
#include "mozilla/RefPtr.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/TaskQueue.h"
namespace mozilla {
// A convenience TaskQueue not requiring explicit shutdown.
class AutoTaskQueue : public AbstractThread
{
public:
explicit AutoTaskQueue(already_AddRefed<SharedThreadPool> aPool, bool aSupportsTailDispatch = false)
: AbstractThread(aSupportsTailDispatch)
, mTaskQueue(new TaskQueue(Move(aPool), aSupportsTailDispatch))
{}
TaskDispatcher& TailDispatcher() override
{
return mTaskQueue->TailDispatcher();
}
void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
DispatchReason aReason = NormalDispatch) override
{
mTaskQueue->Dispatch(Move(aRunnable), aFailureHandling, aReason);
}
// Blocks until all tasks finish executing.
void AwaitIdle() { mTaskQueue->AwaitIdle(); }
bool IsEmpty() { return mTaskQueue->IsEmpty(); }
// Returns true if the current thread is currently running a Runnable in
// the task queue.
bool IsCurrentThreadIn() override { return mTaskQueue->IsCurrentThreadIn(); }
private:
~AutoTaskQueue()
{
RefPtr<TaskQueue> taskqueue = mTaskQueue;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([taskqueue]() { taskqueue->BeginShutdown(); });
AbstractThread::MainThread()->Dispatch(task.forget());
}
RefPtr<TaskQueue> mTaskQueue;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,716 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ContainerParser.h"
#include "WebMBufferedParser.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorResult.h"
#include "mp4_demuxer/MoofParser.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
#include "MediaData.h"
#ifdef MOZ_FMP4
#include "MP4Stream.h"
#include "mp4_demuxer/AtomType.h"
#include "mp4_demuxer/ByteReader.h"
#endif
#include "nsAutoPtr.h"
#include "SourceBufferResource.h"
#include <algorithm>
extern mozilla::LogModule* GetMediaSourceSamplesLog();
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define MSE_DEBUG(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define MSE_DEBUGV(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Verbose, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
namespace mozilla {
ContainerParser::ContainerParser(const nsACString& aType)
: mHasInitData(false)
, mType(aType)
{
}
ContainerParser::~ContainerParser() = default;
MediaResult
ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData)
{
MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
aData->Length(),
aData->Length() > 0 ? (*aData)[0] : 0,
aData->Length() > 1 ? (*aData)[1] : 0,
aData->Length() > 2 ? (*aData)[2] : 0,
aData->Length() > 3 ? (*aData)[3] : 0);
return NS_ERROR_NOT_AVAILABLE;
}
MediaResult
ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData)
{
MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
aData->Length(),
aData->Length() > 0 ? (*aData)[0] : 0,
aData->Length() > 1 ? (*aData)[1] : 0,
aData->Length() > 2 ? (*aData)[2] : 0,
aData->Length() > 3 ? (*aData)[3] : 0);
return NS_ERROR_NOT_AVAILABLE;
}
MediaResult
ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd)
{
return NS_ERROR_NOT_AVAILABLE;
}
bool
ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
return llabs(aLhs - aRhs) <= GetRoundingError();
}
int64_t
ContainerParser::GetRoundingError()
{
NS_WARNING("Using default ContainerParser::GetRoundingError implementation");
return 0;
}
bool
ContainerParser::HasCompleteInitData()
{
return mHasInitData && !!mInitData->Length();
}
MediaByteBuffer*
ContainerParser::InitData()
{
return mInitData;
}
MediaByteRange
ContainerParser::InitSegmentRange()
{
return mCompleteInitSegmentRange;
}
MediaByteRange
ContainerParser::MediaHeaderRange()
{
return mCompleteMediaHeaderRange;
}
MediaByteRange
ContainerParser::MediaSegmentRange()
{
return mCompleteMediaSegmentRange;
}
class WebMContainerParser : public ContainerParser {
public:
explicit WebMContainerParser(const nsACString& aType)
: ContainerParser(aType)
, mParser(0)
, mOffset(0)
{}
static const unsigned NS_PER_USEC = 1000;
static const unsigned USEC_PER_SEC = 1000000;
MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
{
ContainerParser::IsInitSegmentPresent(aData);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size or contain a value large
// enough to include the Segment Information and Tracks
// elements that follow)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
// 0x1a45dfa3 // EBML
if (aData->Length() < 4) {
return NS_ERROR_NOT_AVAILABLE;
}
if ((*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
(*aData)[3] == 0xa3) {
return NS_OK;
}
return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
}
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
ContainerParser::IsMediaSegmentPresent(aData);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
// 0x1f43b675 // Cluster
if (aData->Length() < 4) {
return NS_ERROR_NOT_AVAILABLE;
}
if ((*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
(*aData)[3] == 0x75) {
return NS_OK;
}
// 0x1c53bb6b // Cues
if ((*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
(*aData)[3] == 0x6b) {
return NS_OK;
}
return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
}
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (mLastMapping &&
(initSegment || NS_SUCCEEDED(IsMediaSegmentPresent(aData)))) {
// The last data contained a complete cluster but we can only detect it
// now that a new one is starting.
// We use mOffset as end position to ensure that any blocks not reported
// by WebMBufferParser are properly skipped.
mCompleteMediaSegmentRange = MediaByteRange(mLastMapping.ref().mSyncOffset,
mOffset);
mLastMapping.reset();
MSE_DEBUG(WebMContainerParser, "New cluster found at start, ending previous one");
return NS_ERROR_NOT_AVAILABLE;
}
if (initSegment) {
mOffset = 0;
mParser = WebMBufferedParser(0);
mOverlappedMapping.Clear();
mInitData = new MediaByteBuffer();
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
mCompleteMediaHeaderRange = MediaByteRange();
mCompleteMediaSegmentRange = MediaByteRange();
}
// XXX if it only adds new mappings, overlapped but not available
// (e.g. overlap < 0) frames are "lost" from the reported mappings here.
nsTArray<WebMTimeDataOffset> mapping;
mapping.AppendElements(mOverlappedMapping);
mOverlappedMapping.Clear();
ReentrantMonitor dummy("dummy");
mParser.Append(aData->Elements(), aData->Length(), mapping, dummy);
if (mResource) {
mResource->AppendData(aData);
}
// XXX This is a bit of a hack. Assume if there are no timecodes
// present and it's an init segment that it's _just_ an init segment.
// We should be more precise.
if (initSegment || !HasCompleteInitData()) {
if (mParser.mInitEndOffset > 0) {
MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) {
// Super unlikely OOM
return NS_ERROR_OUT_OF_MEMORY;
}
mCompleteInitSegmentRange = MediaByteRange(0, mParser.mInitEndOffset);
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
MSE_DEBUG(WebMContainerParser, "Stashed init of %u bytes.",
mParser.mInitEndOffset);
mResource = nullptr;
} else {
MSE_DEBUG(WebMContainerParser, "Incomplete init found.");
}
mHasInitData = true;
}
mOffset += aData->Length();
if (mapping.IsEmpty()) {
return NS_ERROR_NOT_AVAILABLE;
}
// Calculate media range for first media segment.
// Check if we have a cluster finishing in the current data.
uint32_t endIdx = mapping.Length() - 1;
bool foundNewCluster = false;
while (mapping[0].mSyncOffset != mapping[endIdx].mSyncOffset) {
endIdx -= 1;
foundNewCluster = true;
}
int32_t completeIdx = endIdx;
while (completeIdx >= 0 && mOffset < mapping[completeIdx].mEndOffset) {
MSE_DEBUG(WebMContainerParser, "block is incomplete, missing: %lld",
mapping[completeIdx].mEndOffset - mOffset);
completeIdx -= 1;
}
// Save parsed blocks for which we do not have all data yet.
mOverlappedMapping.AppendElements(mapping.Elements() + completeIdx + 1,
mapping.Length() - completeIdx - 1);
if (completeIdx < 0) {
mLastMapping.reset();
return NS_ERROR_NOT_AVAILABLE;
}
if (mCompleteMediaHeaderRange.IsEmpty()) {
mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset,
mapping[0].mEndOffset);
}
if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) {
// We now have all information required to delimit a complete cluster.
int64_t endOffset = mapping[endIdx+1].mSyncOffset;
if (mapping[endIdx+1].mInitOffset > mapping[endIdx].mInitOffset) {
// We have a new init segment before this cluster.
endOffset = mapping[endIdx+1].mInitOffset;
}
mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset,
endOffset);
} else if (mapping[endIdx].mClusterEndOffset >= 0 &&
mOffset >= mapping[endIdx].mClusterEndOffset) {
mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset,
mParser.EndSegmentOffset(mapping[endIdx].mClusterEndOffset));
}
Maybe<WebMTimeDataOffset> previousMapping;
if (completeIdx) {
previousMapping = Some(mapping[completeIdx - 1]);
} else {
previousMapping = mLastMapping;
}
mLastMapping = Some(mapping[completeIdx]);
if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
// We have no previous nor next block available,
// so we can't estimate this block's duration.
return NS_ERROR_NOT_AVAILABLE;
}
uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode
: mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
aStart = mapping[0].mTimecode / NS_PER_USEC;
aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;
MSE_DEBUG(WebMContainerParser, "[%lld, %lld] [fso=%lld, leo=%lld, l=%u processedIdx=%u fs=%lld]",
aStart, aEnd, mapping[0].mSyncOffset,
mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
mCompleteMediaSegmentRange.mEnd);
return NS_OK;
}
int64_t GetRoundingError() override
{
int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
return error * 2;
}
private:
WebMBufferedParser mParser;
nsTArray<WebMTimeDataOffset> mOverlappedMapping;
int64_t mOffset;
Maybe<WebMTimeDataOffset> mLastMapping;
};
#ifdef MOZ_FMP4
class MP4ContainerParser : public ContainerParser {
public:
explicit MP4ContainerParser(const nsACString& aType)
: ContainerParser(aType)
{}
MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
{
ContainerParser::IsInitSegmentPresent(aData);
// Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
// file is the 'ftyp' atom followed by a file type. We just check for a
// vaguely valid 'ftyp' atom.
if (aData->Length() < 8) {
return NS_ERROR_NOT_AVAILABLE;
}
AtomParser parser(mType, aData);
if (!parser.IsValid()) {
return MediaResult(
NS_ERROR_FAILURE,
RESULT_DETAIL("Invalid Top-Level Box:%s", parser.LastInvalidBox()));
}
return parser.StartWithInitSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
if (aData->Length() < 8) {
return NS_ERROR_NOT_AVAILABLE;
}
AtomParser parser(mType, aData);
if (!parser.IsValid()) {
return MediaResult(
NS_ERROR_FAILURE,
RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
}
return parser.StartWithMediaSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
private:
class AtomParser {
public:
AtomParser(const nsACString& aType, const MediaByteBuffer* aData)
{
const nsCString mType(aType); // for logging macro.
mp4_demuxer::ByteReader reader(aData);
mp4_demuxer::AtomType initAtom("ftyp");
mp4_demuxer::AtomType mediaAtom("moof");
// Valid top-level boxes defined in ISO/IEC 14496-12 (Table 1)
static const mp4_demuxer::AtomType validBoxes[] = {
"ftyp", "moov", // init segment
"pdin", "free", "sidx", // optional prior moov box
"styp", "moof", "mdat", // media segment
"mfra", "skip", "meta", "meco", "ssix", "prft" // others.
"pssh", // optional with encrypted EME, though ignored.
"emsg", // ISO23009-1:2014 Section 5.10.3.3
"bloc", "uuid" // boxes accepted by chrome.
};
while (reader.Remaining() >= 8) {
uint64_t size = reader.ReadU32();
const uint8_t* typec = reader.Peek(4);
mp4_demuxer::AtomType type(reader.ReadU32());
MSE_DEBUGV(AtomParser ,"Checking atom:'%c%c%c%c' @ %u",
typec[0], typec[1], typec[2], typec[3],
(uint32_t)reader.Offset() - 8);
if (std::find(std::begin(validBoxes), std::end(validBoxes), type)
== std::end(validBoxes)) {
// No valid box found, no point continuing.
mLastInvalidBox[0] = typec[0];
mLastInvalidBox[1] = typec[1];
mLastInvalidBox[2] = typec[2];
mLastInvalidBox[3] = typec[3];
mLastInvalidBox[4] = '\0';
mValid = false;
break;
}
if (mInitOffset.isNothing() &&
mp4_demuxer::AtomType(type) == initAtom) {
mInitOffset = Some(reader.Offset());
}
if (mMediaOffset.isNothing() &&
mp4_demuxer::AtomType(type) == mediaAtom) {
mMediaOffset = Some(reader.Offset());
}
if (mInitOffset.isSome() && mMediaOffset.isSome()) {
// We have everything we need.
break;
}
if (size == 1) {
// 64 bits size.
if (!reader.CanReadType<uint64_t>()) {
break;
}
size = reader.ReadU64();
} else if (size == 0) {
// Atom extends to the end of the buffer, it can't have what we're
// looking for.
break;
}
if (reader.Remaining() < size - 8) {
// Incomplete atom.
break;
}
reader.Read(size - 8);
}
}
bool StartWithInitSegment() const
{
return mInitOffset.isSome() &&
(mMediaOffset.isNothing() || mInitOffset.ref() < mMediaOffset.ref());
}
bool StartWithMediaSegment() const
{
return mMediaOffset.isSome() &&
(mInitOffset.isNothing() || mMediaOffset.ref() < mInitOffset.ref());
}
bool IsValid() const { return mValid; }
const char* LastInvalidBox() const { return mLastInvalidBox; }
private:
Maybe<size_t> mInitOffset;
Maybe<size_t> mMediaOffset;
bool mValid = true;
char mLastInvalidBox[5];
};
public:
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (initSegment) {
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
mStream = new MP4Stream(mResource);
// We use a timestampOffset of 0 for ContainerParser, and require
// consumers of ParseStartAndEndTimestamps to add their timestamp offset
// manually. This allows the ContainerParser to be shared across different
// timestampOffsets.
mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false);
mInitData = new MediaByteBuffer();
} else if (!mStream || !mParser) {
return NS_ERROR_NOT_AVAILABLE;
}
mResource->AppendData(aData);
MediaByteRangeSet byteRanges;
byteRanges +=
MediaByteRange(int64_t(mParser->mOffset), mResource->GetLength());
mParser->RebuildFragmentedIndex(byteRanges);
if (initSegment || !HasCompleteInitData()) {
MediaByteRange& range = mParser->mInitRange;
if (range.Length()) {
mCompleteInitSegmentRange = range;
if (!mInitData->SetLength(range.Length(), fallible)) {
// Super unlikely OOM
return NS_ERROR_OUT_OF_MEMORY;
}
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
mResource->ReadFromCache(buffer, range.mStart, range.Length());
MSE_DEBUG(MP4ContainerParser ,"Stashed init of %u bytes.",
range.Length());
} else {
MSE_DEBUG(MP4ContainerParser, "Incomplete init found.");
}
mHasInitData = true;
}
mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
mParser->GetCompositionRange(byteRanges);
mCompleteMediaHeaderRange = mParser->FirstCompleteMediaHeader();
mCompleteMediaSegmentRange = mParser->FirstCompleteMediaSegment();
ErrorResult rv;
if (HasCompleteInitData()) {
mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
}
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return NS_ERROR_OUT_OF_MEMORY;
}
if (compositionRange.IsNull()) {
return NS_ERROR_NOT_AVAILABLE;
}
aStart = compositionRange.start;
aEnd = compositionRange.end;
MSE_DEBUG(MP4ContainerParser, "[%lld, %lld]",
aStart, aEnd);
return NS_OK;
}
// Gaps of up to 35ms (marginally longer than a single frame at 30fps) are considered
// to be sequential frames.
int64_t GetRoundingError() override
{
return 35000;
}
private:
RefPtr<MP4Stream> mStream;
nsAutoPtr<mp4_demuxer::MoofParser> mParser;
};
#endif // MOZ_FMP4
#ifdef MOZ_FMP4
class ADTSContainerParser : public ContainerParser {
public:
explicit ADTSContainerParser(const nsACString& aType)
: ContainerParser(aType)
{}
typedef struct {
size_t header_length; // Length of just the initialization data.
size_t frame_length; // Includes header_length;
uint8_t aac_frames; // Number of AAC frames in the ADTS frame.
bool have_crc;
} Header;
/// Helper to parse the ADTS header, returning data we care about.
/// Returns true if the header is parsed successfully.
/// Returns false if the header is invalid or incomplete,
/// without modifying the passed-in Header object.
bool Parse(MediaByteBuffer* aData, Header& header)
{
MOZ_ASSERT(aData);
// ADTS initialization segments are just the packet header.
if (aData->Length() < 7) {
MSE_DEBUG(ADTSContainerParser, "buffer too short for header.");
return false;
}
// Check 0xfffx sync word plus layer 0.
if (((*aData)[0] != 0xff) || (((*aData)[1] & 0xf6) != 0xf0)) {
MSE_DEBUG(ADTSContainerParser, "no syncword.");
return false;
}
bool have_crc = !((*aData)[1] & 0x01);
if (have_crc && aData->Length() < 9) {
MSE_DEBUG(ADTSContainerParser, "buffer too short for header with crc.");
return false;
}
uint8_t frequency_index = ((*aData)[2] & 0x3c) >> 2;
MOZ_ASSERT(frequency_index < 16);
if (frequency_index == 15) {
MSE_DEBUG(ADTSContainerParser, "explicit frequency disallowed.");
return false;
}
size_t header_length = have_crc ? 9 : 7;
size_t data_length = (((*aData)[3] & 0x03) << 11) ||
(((*aData)[4] & 0xff) << 3) ||
(((*aData)[5] & 0xe0) >> 5);
uint8_t frames = ((*aData)[6] & 0x03) + 1;
MOZ_ASSERT(frames > 0);
MOZ_ASSERT(frames < 4);
// Return successfully parsed data.
header.header_length = header_length;
header.frame_length = header_length + data_length;
header.aac_frames = frames;
header.have_crc = have_crc;
return true;
}
MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
{
// Call superclass for logging.
ContainerParser::IsInitSegmentPresent(aData);
Header header;
if (!Parse(aData, header)) {
return NS_ERROR_NOT_AVAILABLE;
}
MSE_DEBUGV(ADTSContainerParser, "%llu byte frame %d aac frames%s",
(unsigned long long)header.frame_length, (int)header.aac_frames,
header.have_crc ? " crc" : "");
return NS_OK;
}
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
// Call superclass for logging.
ContainerParser::IsMediaSegmentPresent(aData);
// Make sure we have a header so we know how long the frame is.
// NB this assumes the media segment buffer starts with an
// initialization segment. Since every frame has an ADTS header
// this is a normal place to divide packets, but we can re-parse
// mInitData if we need to handle separate media segments.
Header header;
if (!Parse(aData, header)) {
return NS_ERROR_NOT_AVAILABLE;
}
// We're supposed to return true as long as aData contains the
// start of a media segment, whether or not it's complete. So
// return true if we have any data beyond the header.
if (aData->Length() <= header.header_length) {
return NS_ERROR_NOT_AVAILABLE;
}
// We should have at least a partial frame.
return NS_OK;
}
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
// ADTS header.
Header header;
if (!Parse(aData, header)) {
return NS_ERROR_NOT_AVAILABLE;
}
mHasInitData = true;
mCompleteInitSegmentRange = MediaByteRange(0, int64_t(header.header_length));
// Cache raw header in case the caller wants a copy.
mInitData = new MediaByteBuffer(header.header_length);
mInitData->AppendElements(aData->Elements(), header.header_length);
// Check that we have enough data for the frame body.
if (aData->Length() < header.frame_length) {
MSE_DEBUGV(ADTSContainerParser, "Not enough data for %llu byte frame"
" in %llu byte buffer.",
(unsigned long long)header.frame_length,
(unsigned long long)(aData->Length()));
return NS_ERROR_NOT_AVAILABLE;
}
mCompleteMediaSegmentRange = MediaByteRange(header.header_length,
header.frame_length);
// The ADTS MediaSource Byte Stream Format document doesn't
// define media header. Just treat it the same as the whole
// media segment.
mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
MSE_DEBUG(ADTSContainerParser, "[%lld, %lld]",
aStart, aEnd);
// We don't update timestamps, regardless.
return NS_ERROR_NOT_AVAILABLE;
}
// Audio shouldn't have gaps.
// Especially when we generate the timestamps ourselves.
int64_t GetRoundingError() override
{
return 0;
}
};
#endif // MOZ_FMP4
/*static*/ ContainerParser*
ContainerParser::CreateForMIMEType(const nsACString& aType)
{
if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
return new WebMContainerParser(aType);
}
#ifdef MOZ_FMP4
if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
return new MP4ContainerParser(aType);
}
if (aType.LowerCaseEqualsLiteral("audio/aac")) {
return new ADTSContainerParser(aType);
}
#endif
return new ContainerParser(aType);
}
#undef MSE_DEBUG
#undef MSE_DEBUGV
} // namespace mozilla

View file

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_CONTAINERPARSER_H_
#define MOZILLA_CONTAINERPARSER_H_
#include "mozilla/RefPtr.h"
#include "nsString.h"
#include "MediaResource.h"
#include "MediaResult.h"
namespace mozilla {
class MediaByteBuffer;
class SourceBufferResource;
class ContainerParser {
public:
explicit ContainerParser(const nsACString& aType);
virtual ~ContainerParser();
// Return true if aData starts with an initialization segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
// Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
// data is currently available to make a determination. Any other value
// indicates an error.
virtual MediaResult IsInitSegmentPresent(MediaByteBuffer* aData);
// Return true if aData starts with a media segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
// Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
// data is currently available to make a determination. Any other value
// indicates an error.
virtual MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData);
// Parse aData to extract the start and end frame times from the media
// segment. aData may not start on a parser sync boundary. Return NS_OK
// if aStart and aEnd have been updated and NS_ERROR_NOT_AVAILABLE otherwise
// when no error were encountered.
virtual MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd);
// Compare aLhs and rHs, considering any error that may exist in the
// timestamps from the format's base representation. Return true if aLhs
// == aRhs within the error epsilon.
bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
virtual int64_t GetRoundingError();
MediaByteBuffer* InitData();
bool HasInitData()
{
return mHasInitData;
}
// Return true if a complete initialization segment has been passed
// to ParseStartAndEndTimestamps(). The calls below to retrieve
// MediaByteRanges will be valid from when this call first succeeds.
bool HasCompleteInitData();
// Returns the byte range of the first complete init segment, or an empty
// range if not complete.
MediaByteRange InitSegmentRange();
// Returns the byte range of the first complete media segment header,
// or an empty range if not complete.
MediaByteRange MediaHeaderRange();
// Returns the byte range of the first complete media segment or an empty
// range if not complete.
MediaByteRange MediaSegmentRange();
static ContainerParser* CreateForMIMEType(const nsACString& aType);
protected:
RefPtr<MediaByteBuffer> mInitData;
RefPtr<SourceBufferResource> mResource;
bool mHasInitData;
MediaByteRange mCompleteInitSegmentRange;
MediaByteRange mCompleteMediaHeaderRange;
MediaByteRange mCompleteMediaSegmentRange;
const nsCString mType;
};
} // namespace mozilla
#endif /* MOZILLA_CONTAINERPARSER_H_ */

View file

@ -0,0 +1,591 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "MediaSource.h"
#include "AsyncEventRunner.h"
#include "DecoderTraits.h"
#include "Benchmark.h"
#include "DecoderDoctorDiagnostics.h"
#include "MediaContentType.h"
#include "MediaResult.h"
#include "MediaSourceUtils.h"
#include "SourceBuffer.h"
#include "SourceBufferList.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/mozalloc.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIRunnable.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsPIDOMWindow.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "mozilla/Logging.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/Sprintf.h"
#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#endif
struct JSContext;
class JSObject;
mozilla::LogModule* GetMediaSourceLog()
{
static mozilla::LazyLogModule sLogModule("MediaSource");
return sLogModule;
}
mozilla::LogModule* GetMediaSourceAPILog()
{
static mozilla::LazyLogModule sLogModule("MediaSource");
return sLogModule;
}
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSource(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("MediaSource(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
// Arbitrary limit.
static const unsigned int MAX_SOURCE_BUFFERS = 16;
namespace mozilla {
// Returns true if we should enable MSE webm regardless of preferences.
// 1. If MP4/H264 isn't supported:
// * Windows XP
// * Windows Vista and Server 2008 without the optional "Platform Update Supplement"
// * N/KN editions (Europe and Korea) of Windows 7/8/8.1/10 without the
// optional "Windows Media Feature Pack"
// 2. If H264 hardware acceleration is not available.
// 3. The CPU is considered to be fast enough
static bool
IsWebMForced(DecoderDoctorDiagnostics* aDiagnostics)
{
bool mp4supported =
DecoderTraits::IsMP4TypeAndEnabled(NS_LITERAL_CSTRING("video/mp4"),
aDiagnostics);
bool hwsupported = gfx::gfxVars::CanUseHardwareVideoDecoding();
#ifdef MOZ_WIDGET_ANDROID
return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast() ||
java::HardwareCodecCapabilityUtils::HasHWVP9();
#else
return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast();
#endif
}
namespace dom {
/* static */
nsresult
MediaSource::IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
{
if (aType.IsEmpty()) {
return NS_ERROR_DOM_TYPE_ERR;
}
MediaContentType contentType{aType};
if (!contentType.IsValid()) {
return NS_ERROR_DOM_TYPE_ERR;
}
if (DecoderTraits::CanHandleContentType(contentType, aDiagnostics)
== CANPLAY_NO) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
// Now we know that this media type could be played.
// MediaSource imposes extra restrictions, and some prefs.
const nsACString& mimeType = contentType.GetMIMEType();
if (mimeType.EqualsASCII("video/mp4") || mimeType.EqualsASCII("audio/mp4")) {
if (!Preferences::GetBool("media.mediasource.mp4.enabled", false)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
return NS_OK;
}
if (mimeType.EqualsASCII("video/webm")) {
if (!(Preferences::GetBool("media.mediasource.webm.enabled", false) ||
IsWebMForced(aDiagnostics))) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
return NS_OK;
}
if (mimeType.EqualsASCII("audio/webm")) {
if (!(Preferences::GetBool("media.mediasource.webm.enabled", false) ||
Preferences::GetBool("media.mediasource.webm.audio.enabled", true))) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
return NS_OK;
}
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
/* static */ already_AddRefed<MediaSource>
MediaSource::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<MediaSource> mediaSource = new MediaSource(window);
return mediaSource.forget();
}
MediaSource::~MediaSource()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("");
if (mDecoder) {
mDecoder->DetachMediaSource();
}
}
SourceBufferList*
MediaSource::SourceBuffers()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mSourceBuffers->IsEmpty());
return mSourceBuffers;
}
SourceBufferList*
MediaSource::ActiveSourceBuffers()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mActiveSourceBuffers->IsEmpty());
return mActiveSourceBuffers;
}
MediaSourceReadyState
MediaSource::ReadyState()
{
MOZ_ASSERT(NS_IsMainThread());
return mReadyState;
}
double
MediaSource::Duration()
{
MOZ_ASSERT(NS_IsMainThread());
if (mReadyState == MediaSourceReadyState::Closed) {
return UnspecifiedNaN<double>();
}
MOZ_ASSERT(mDecoder);
return mDecoder->GetDuration();
}
void
MediaSource::SetDuration(double aDuration, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetDuration(aDuration=%f, ErrorResult)", aDuration);
if (aDuration < 0 || IsNaN(aDuration)) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
if (mReadyState != MediaSourceReadyState::Open ||
mSourceBuffers->AnyUpdating()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
DurationChange(aDuration, aRv);
}
void
MediaSource::SetDuration(double aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetDuration(aDuration=%f)", aDuration);
mDecoder->SetMediaSourceDuration(aDuration);
}
already_AddRefed<SourceBuffer>
MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
DecoderDoctorDiagnostics diagnostics;
nsresult rv = IsTypeSupported(aType, &diagnostics);
diagnostics.StoreFormatDiagnostics(GetOwner()
? GetOwner()->GetExtantDoc()
: nullptr,
aType, NS_SUCCEEDED(rv), __func__);
MSE_API("AddSourceBuffer(aType=%s)%s",
NS_ConvertUTF16toUTF8(aType).get(),
rv == NS_OK ? "" : " [not supported]");
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return nullptr;
}
if (mReadyState != MediaSourceReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
MediaContentType contentType{aType};
if (!contentType.IsValid()) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
const nsACString& mimeType = contentType.GetMIMEType();
RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, mimeType);
if (!sourceBuffer) {
aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
return nullptr;
}
mSourceBuffers->Append(sourceBuffer);
MSE_DEBUG("sourceBuffer=%p", sourceBuffer.get());
return sourceBuffer.forget();
}
void
MediaSource::SourceBufferIsActive(SourceBuffer* aSourceBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
mActiveSourceBuffers->ClearSimple();
bool found = false;
for (uint32_t i = 0; i < mSourceBuffers->Length(); i++) {
SourceBuffer* sourceBuffer = mSourceBuffers->IndexedGetter(i, found);
MOZ_ALWAYS_TRUE(found);
if (sourceBuffer == aSourceBuffer) {
mActiveSourceBuffers->Append(aSourceBuffer);
} else if (sourceBuffer->IsActive()) {
mActiveSourceBuffers->AppendSimple(sourceBuffer);
}
}
}
void
MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
SourceBuffer* sourceBuffer = &aSourceBuffer;
MSE_API("RemoveSourceBuffer(aSourceBuffer=%p)", sourceBuffer);
if (!mSourceBuffers->Contains(sourceBuffer)) {
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
sourceBuffer->AbortBufferAppend();
// TODO:
// abort stream append loop (if running)
// TODO:
// For all sourceBuffer audioTracks, videoTracks, textTracks:
// set sourceBuffer to null
// remove sourceBuffer video, audio, text Tracks from MediaElement tracks
// remove sourceBuffer video, audio, text Tracks and fire "removetrack" at affected lists
// fire "removetrack" at modified MediaElement track lists
// If removed enabled/selected, fire "change" at affected MediaElement list.
if (mActiveSourceBuffers->Contains(sourceBuffer)) {
mActiveSourceBuffers->Remove(sourceBuffer);
}
mSourceBuffers->Remove(sourceBuffer);
// TODO: Free all resources associated with sourceBuffer
}
void
MediaSource::EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("EndOfStream(aError=%d)",
aError.WasPassed() ? uint32_t(aError.Value()) : 0);
if (mReadyState != MediaSourceReadyState::Open ||
mSourceBuffers->AnyUpdating()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
SetReadyState(MediaSourceReadyState::Ended);
mSourceBuffers->Ended();
if (!aError.WasPassed()) {
DurationChange(mSourceBuffers->GetHighestBufferedEndTime(), aRv);
// Notify reader that all data is now available.
mDecoder->Ended(true);
return;
}
switch (aError.Value()) {
case MediaSourceEndOfStreamError::Network:
mDecoder->NetworkError();
break;
case MediaSourceEndOfStreamError::Decode:
mDecoder->DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR);
break;
default:
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
}
}
void
MediaSource::EndOfStream(const MediaResult& aError)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("EndOfStream(aError=%d)", aError.Code());
SetReadyState(MediaSourceReadyState::Ended);
mSourceBuffers->Ended();
mDecoder->DecodeError(aError);
}
/* static */ bool
MediaSource::IsTypeSupported(const GlobalObject& aOwner, const nsAString& aType)
{
MOZ_ASSERT(NS_IsMainThread());
DecoderDoctorDiagnostics diagnostics;
nsresult rv = IsTypeSupported(aType, &diagnostics);
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aOwner.GetAsSupports());
diagnostics.StoreFormatDiagnostics(window ? window->GetExtantDoc() : nullptr,
aType, NS_SUCCEEDED(rv), __func__);
#define this nullptr
MSE_API("IsTypeSupported(aType=%s)%s ",
NS_ConvertUTF16toUTF8(aType).get(), rv == NS_OK ? "OK" : "[not supported]");
#undef this // don't ever remove this line !
return NS_SUCCEEDED(rv);
}
/* static */ bool
MediaSource::Enabled(JSContext* cx, JSObject* aGlobal)
{
return Preferences::GetBool("media.mediasource.enabled");
}
void
MediaSource::SetLiveSeekableRange(double aStart, double aEnd, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
// 1. If the readyState attribute is not "open" then throw an InvalidStateError
// exception and abort these steps.
if (mReadyState != MediaSourceReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// 2. If start is negative or greater than end, then throw a TypeError
// exception and abort these steps.
if (aStart < 0 || aStart > aEnd) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
// 3. Set live seekable range to be a new normalized TimeRanges object
// containing a single range whose start position is start and end position is
// end.
mLiveSeekableRange =
Some(media::TimeInterval(media::TimeUnit::FromSeconds(aStart),
media::TimeUnit::FromSeconds(aEnd)));
}
void
MediaSource::ClearLiveSeekableRange(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
// 1. If the readyState attribute is not "open" then throw an InvalidStateError
// exception and abort these steps.
if (mReadyState != MediaSourceReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// 2. If live seekable range contains a range, then set live seekable range to
// be a new empty TimeRanges object.
mLiveSeekableRange.reset();
}
bool
MediaSource::Attach(MediaSourceDecoder* aDecoder)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Attach(aDecoder=%p) owner=%p", aDecoder, aDecoder->GetOwner());
MOZ_ASSERT(aDecoder);
MOZ_ASSERT(aDecoder->GetOwner());
if (mReadyState != MediaSourceReadyState::Closed) {
return false;
}
MOZ_ASSERT(!mMediaElement);
mMediaElement = aDecoder->GetOwner()->GetMediaElement();
MOZ_ASSERT(!mDecoder);
mDecoder = aDecoder;
mDecoder->AttachMediaSource(this);
SetReadyState(MediaSourceReadyState::Open);
return true;
}
void
MediaSource::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("mDecoder=%p owner=%p",
mDecoder.get(), mDecoder ? mDecoder->GetOwner() : nullptr);
if (!mDecoder) {
MOZ_ASSERT(mReadyState == MediaSourceReadyState::Closed);
MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty());
return;
}
mMediaElement = nullptr;
SetReadyState(MediaSourceReadyState::Closed);
if (mActiveSourceBuffers) {
mActiveSourceBuffers->Clear();
}
if (mSourceBuffers) {
mSourceBuffers->Clear();
}
mDecoder->DetachMediaSource();
mDecoder = nullptr;
}
MediaSource::MediaSource(nsPIDOMWindowInner* aWindow)
: DOMEventTargetHelper(aWindow)
, mDecoder(nullptr)
, mPrincipal(nullptr)
, mReadyState(MediaSourceReadyState::Closed)
{
MOZ_ASSERT(NS_IsMainThread());
mSourceBuffers = new SourceBufferList(this);
mActiveSourceBuffers = new SourceBufferList(this);
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
if (sop) {
mPrincipal = sop->GetPrincipal();
}
MSE_API("MediaSource(aWindow=%p) mSourceBuffers=%p mActiveSourceBuffers=%p",
aWindow, mSourceBuffers.get(), mActiveSourceBuffers.get());
}
void
MediaSource::SetReadyState(MediaSourceReadyState aState)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aState != mReadyState);
MSE_DEBUG("SetReadyState(aState=%d) mReadyState=%d", aState, mReadyState);
MediaSourceReadyState oldState = mReadyState;
mReadyState = aState;
if (mReadyState == MediaSourceReadyState::Open &&
(oldState == MediaSourceReadyState::Closed ||
oldState == MediaSourceReadyState::Ended)) {
QueueAsyncSimpleEvent("sourceopen");
if (oldState == MediaSourceReadyState::Ended) {
// Notify reader that more data may come.
mDecoder->Ended(false);
}
return;
}
if (mReadyState == MediaSourceReadyState::Ended &&
oldState == MediaSourceReadyState::Open) {
QueueAsyncSimpleEvent("sourceended");
return;
}
if (mReadyState == MediaSourceReadyState::Closed &&
(oldState == MediaSourceReadyState::Open ||
oldState == MediaSourceReadyState::Ended)) {
QueueAsyncSimpleEvent("sourceclose");
return;
}
NS_WARNING("Invalid MediaSource readyState transition");
}
void
MediaSource::DispatchSimpleEvent(const char* aName)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("Dispatch event '%s'", aName);
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
}
void
MediaSource::QueueAsyncSimpleEvent(const char* aName)
{
MSE_DEBUG("Queuing event '%s'", aName);
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName);
NS_DispatchToMainThread(event);
}
void
MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("DurationChange(aNewDuration=%f)", aNewDuration);
// 1. If the current value of duration is equal to new duration, then return.
if (mDecoder->GetDuration() == aNewDuration) {
return;
}
// 2. If new duration is less than the highest starting presentation timestamp
// of any buffered coded frames for all SourceBuffer objects in sourceBuffers,
// then throw an InvalidStateError exception and abort these steps.
if (aNewDuration < mSourceBuffers->HighestStartTime()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// 3. Let highest end time be the largest track buffer ranges end time across
// all the track buffers across all SourceBuffer objects in sourceBuffers.
double highestEndTime = mSourceBuffers->HighestEndTime();
// 4. If new duration is less than highest end time, then
// 4.1 Update new duration to equal highest end time.
aNewDuration =
std::max(aNewDuration, highestEndTime);
// 5. Update the media duration to new duration and run the HTMLMediaElement
// duration change algorithm.
mDecoder->SetMediaSourceDuration(aNewDuration);
}
void
MediaSource::GetMozDebugReaderData(nsAString& aString)
{
mDecoder->GetMozDebugReaderData(aString);
}
nsPIDOMWindowInner*
MediaSource::GetParentObject() const
{
return GetOwner();
}
JSObject*
MediaSource::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return MediaSourceBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaSource, DOMEventTargetHelper,
mMediaElement,
mSourceBuffers, mActiveSourceBuffers)
NS_IMPL_ADDREF_INHERITED(MediaSource, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(MediaSource, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaSource)
NS_INTERFACE_MAP_ENTRY(mozilla::dom::MediaSource)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
#undef MSE_DEBUG
#undef MSE_API
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_MediaSource_h_
#define mozilla_dom_MediaSource_h_
#include "MediaSourceDecoder.h"
#include "js/RootingAPI.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/MediaSourceBinding.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsID.h"
#include "nsISupports.h"
#include "nscore.h"
#include "TimeUnits.h"
struct JSContext;
class JSObject;
class nsPIDOMWindowInner;
namespace mozilla {
class ErrorResult;
template <typename T> class AsyncEventRunner;
class MediaResult;
namespace dom {
class GlobalObject;
class SourceBuffer;
class SourceBufferList;
template <typename T> class Optional;
#define MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID \
{ 0x3839d699, 0x22c5, 0x439f, \
{ 0x94, 0xca, 0x0e, 0x0b, 0x26, 0xf9, 0xca, 0xbf } }
class MediaSource final : public DOMEventTargetHelper
{
public:
/** WebIDL Methods. */
static already_AddRefed<MediaSource>
Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv);
SourceBufferList* SourceBuffers();
SourceBufferList* ActiveSourceBuffers();
MediaSourceReadyState ReadyState();
double Duration();
void SetDuration(double aDuration, ErrorResult& aRv);
already_AddRefed<SourceBuffer> AddSourceBuffer(const nsAString& aType, ErrorResult& aRv);
void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv);
void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
void EndOfStream(const MediaResult& aError);
void SetLiveSeekableRange(double aStart, double aEnd, ErrorResult& aRv);
void ClearLiveSeekableRange(ErrorResult& aRv);
static bool IsTypeSupported(const GlobalObject&, const nsAString& aType);
static nsresult IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics);
static bool Enabled(JSContext* cx, JSObject* aGlobal);
IMPL_EVENT_HANDLER(sourceopen);
IMPL_EVENT_HANDLER(sourceended);
IMPL_EVENT_HANDLER(sourceclosed);
/** End WebIDL Methods. */
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaSource, DOMEventTargetHelper)
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
nsPIDOMWindowInner* GetParentObject() const;
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
// Attach this MediaSource to Decoder aDecoder. Returns false if already attached.
bool Attach(MediaSourceDecoder* aDecoder);
void Detach();
// Set mReadyState to aState and fire the required events at the MediaSource.
void SetReadyState(MediaSourceReadyState aState);
// Used by SourceBuffer to call CreateSubDecoder.
MediaSourceDecoder* GetDecoder()
{
return mDecoder;
}
nsIPrincipal* GetPrincipal()
{
return mPrincipal;
}
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
bool HasLiveSeekableRange() const { return mLiveSeekableRange.isSome(); }
media::TimeInterval LiveSeekableRange() const
{
return mLiveSeekableRange.value();
}
private:
// SourceBuffer uses SetDuration and SourceBufferIsActive
friend class mozilla::dom::SourceBuffer;
~MediaSource();
explicit MediaSource(nsPIDOMWindowInner* aWindow);
friend class AsyncEventRunner<MediaSource>;
void DispatchSimpleEvent(const char* aName);
void QueueAsyncSimpleEvent(const char* aName);
void DurationChange(double aNewDuration, ErrorResult& aRv);
// SetDuration with no checks.
void SetDuration(double aDuration);
// Mark SourceBuffer as active and rebuild ActiveSourceBuffers.
void SourceBufferIsActive(SourceBuffer* aSourceBuffer);
RefPtr<SourceBufferList> mSourceBuffers;
RefPtr<SourceBufferList> mActiveSourceBuffers;
RefPtr<MediaSourceDecoder> mDecoder;
// Ensures the media element remains alive to dispatch progress and
// durationchanged events.
RefPtr<HTMLMediaElement> mMediaElement;
RefPtr<nsIPrincipal> mPrincipal;
MediaSourceReadyState mReadyState;
Maybe<media::TimeInterval> mLiveSeekableRange;
};
NS_DEFINE_STATIC_IID_ACCESSOR(MediaSource, MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_MediaSource_h_ */

View file

@ -0,0 +1,361 @@
/* -*- mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "MediaSourceDecoder.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "MediaDecoderStateMachine.h"
#include "MediaShutdownManager.h"
#include "MediaSource.h"
#include "MediaSourceResource.h"
#include "MediaSourceUtils.h"
#include "VideoUtils.h"
#include "MediaSourceDemuxer.h"
#include "SourceBufferList.h"
#include <algorithm>
extern mozilla::LogModule* GetMediaSourceLog();
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
using namespace mozilla::media;
namespace mozilla {
MediaSourceDecoder::MediaSourceDecoder(dom::HTMLMediaElement* aElement)
: MediaDecoder(aElement)
, mMediaSource(nullptr)
, mEnded(false)
{
mExplicitDuration.Set(Some(UnspecifiedNaN<double>()));
}
MediaDecoder*
MediaSourceDecoder::Clone(MediaDecoderOwner* aOwner)
{
// TODO: Sort out cloning.
return nullptr;
}
MediaDecoderStateMachine*
MediaSourceDecoder::CreateStateMachine()
{
MOZ_ASSERT(NS_IsMainThread());
mDemuxer = new MediaSourceDemuxer();
mReader = new MediaFormatReader(this, mDemuxer, GetVideoFrameContainer());
return new MediaDecoderStateMachine(this, mReader);
}
nsresult
MediaSourceDecoder::Load(nsIStreamListener**)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!GetStateMachine());
nsresult rv = MediaShutdownManager::Instance().Register(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
SetStateMachine(CreateStateMachine());
if (!GetStateMachine()) {
NS_WARNING("Failed to create state machine!");
return NS_ERROR_FAILURE;
}
rv = GetStateMachine()->Init(this);
NS_ENSURE_SUCCESS(rv, rv);
SetStateMachineParameters();
return NS_OK;
}
media::TimeIntervals
MediaSourceDecoder::GetSeekable()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mMediaSource) {
NS_WARNING("MediaSource element isn't attached");
return media::TimeIntervals::Invalid();
}
media::TimeIntervals seekable;
double duration = mMediaSource->Duration();
if (IsNaN(duration)) {
// Return empty range.
} else if (duration > 0 && mozilla::IsInfinite(duration)) {
media::TimeIntervals buffered = GetBuffered();
// 1. If live seekable range is not empty:
if (mMediaSource->HasLiveSeekableRange()) {
// 1. Let union ranges be the union of live seekable range and the
// HTMLMediaElement.buffered attribute.
media::TimeIntervals unionRanges =
buffered + mMediaSource->LiveSeekableRange();
// 2. Return a single range with a start time equal to the earliest start
// time in union ranges and an end time equal to the highest end time in
// union ranges and abort these steps.
seekable +=
media::TimeInterval(unionRanges.GetStart(), unionRanges.GetEnd());
return seekable;
}
if (buffered.Length()) {
seekable +=
media::TimeInterval(media::TimeUnit::FromSeconds(0), buffered.GetEnd());
}
} else {
seekable += media::TimeInterval(media::TimeUnit::FromSeconds(0),
media::TimeUnit::FromSeconds(duration));
}
MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
return seekable;
}
media::TimeIntervals
MediaSourceDecoder::GetBuffered()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mMediaSource) {
NS_WARNING("MediaSource element isn't attached");
return media::TimeIntervals::Invalid();
}
dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
if (!sourceBuffers) {
// Media source object is shutting down.
return TimeIntervals();
}
media::TimeUnit highestEndTime;
nsTArray<media::TimeIntervals> activeRanges;
media::TimeIntervals buffered;
for (uint32_t i = 0; i < sourceBuffers->Length(); i++) {
bool found;
dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found);
MOZ_ASSERT(found);
activeRanges.AppendElement(sb->GetTimeIntervals());
highestEndTime =
std::max(highestEndTime, activeRanges.LastElement().GetEnd());
}
buffered +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(0), highestEndTime);
for (auto& range : activeRanges) {
if (mEnded && range.Length()) {
// Set the end time on the last range to highestEndTime by adding a
// new range spanning the current end time to highestEndTime, which
// Normalize() will then merge with the old last range.
range +=
media::TimeInterval(range.GetEnd(), highestEndTime);
}
buffered.Intersection(range);
}
MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get());
return buffered;
}
void
MediaSourceDecoder::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Shutdown");
// Detach first so that TrackBuffers are unused on the main thread when
// shut down on the decode task queue.
if (mMediaSource) {
mMediaSource->Detach();
}
mDemuxer = nullptr;
MediaDecoder::Shutdown();
}
/*static*/
already_AddRefed<MediaResource>
MediaSourceDecoder::CreateResource(nsIPrincipal* aPrincipal)
{
return RefPtr<MediaResource>(new MediaSourceResource(aPrincipal)).forget();
}
void
MediaSourceDecoder::AttachMediaSource(dom::MediaSource* aMediaSource)
{
MOZ_ASSERT(!mMediaSource && !GetStateMachine() && NS_IsMainThread());
mMediaSource = aMediaSource;
}
void
MediaSourceDecoder::DetachMediaSource()
{
MOZ_ASSERT(mMediaSource && NS_IsMainThread());
mMediaSource = nullptr;
}
void
MediaSourceDecoder::Ended(bool aEnded)
{
MOZ_ASSERT(NS_IsMainThread());
static_cast<MediaSourceResource*>(GetResource())->SetEnded(aEnded);
if (aEnded) {
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
NotifyDataArrived();
}
mEnded = aEnded;
}
void
MediaSourceDecoder::AddSizeOfResources(ResourceSizes* aSizes)
{
MOZ_ASSERT(NS_IsMainThread());
if (GetDemuxer()) {
GetDemuxer()->AddSizeOfResources(aSizes);
}
}
void
MediaSourceDecoder::SetInitialDuration(int64_t aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
// Only use the decoded duration if one wasn't already
// set.
if (!mMediaSource || !IsNaN(ExplicitDuration())) {
return;
}
double duration = aDuration;
// A duration of -1 is +Infinity.
if (aDuration >= 0) {
duration /= USECS_PER_S;
}
SetMediaSourceDuration(duration);
}
void
MediaSourceDecoder::SetMediaSourceDuration(double aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdown());
if (aDuration >= 0) {
int64_t checkedDuration;
if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
// INT64_MAX is used as infinity by the state machine.
// We want a very bigger number, but not infinity.
checkedDuration = INT64_MAX - 1;
}
SetExplicitDuration(aDuration);
} else {
SetExplicitDuration(PositiveInfinity<double>());
}
}
void
MediaSourceDecoder::GetMozDebugReaderData(nsAString& aString)
{
if (mReader && mDemuxer) {
mReader->GetMozDebugReaderData(aString);
mDemuxer->GetMozDebugReaderData(aString);
}
}
double
MediaSourceDecoder::GetDuration()
{
MOZ_ASSERT(NS_IsMainThread());
return ExplicitDuration();
}
MediaDecoderOwner::NextFrameStatus
MediaSourceDecoder::NextFrameBufferedStatus()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mMediaSource ||
mMediaSource->ReadyState() == dom::MediaSourceReadyState::Closed) {
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
}
// Next frame hasn't been decoded yet.
// Use the buffered range to consider if we have the next frame available.
TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
TimeIntervals buffered = GetBuffered();
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
TimeInterval interval(
currentPosition,
currentPosition
+ media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
return buffered.ContainsStrict(ClampIntervalToEnd(interval))
? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
}
bool
MediaSourceDecoder::CanPlayThrough()
{
MOZ_ASSERT(NS_IsMainThread());
if (NextFrameBufferedStatus() == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) {
return false;
}
if (IsNaN(mMediaSource->Duration())) {
// Don't have any data yet.
return false;
}
TimeUnit duration = TimeUnit::FromSeconds(mMediaSource->Duration());
TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
if (duration.IsInfinite()) {
// We can't make an informed decision and just assume that it's a live stream
return true;
} else if (duration <= currentPosition) {
return true;
}
// If we have data up to the mediasource's duration or 30s ahead, we can
// assume that we can play without interruption.
TimeIntervals buffered = GetBuffered();
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
TimeUnit timeAhead =
std::min(duration, currentPosition + TimeUnit::FromSeconds(30));
TimeInterval interval(currentPosition, timeAhead);
return buffered.ContainsStrict(ClampIntervalToEnd(interval));
}
void
MediaSourceDecoder::NotifyWaitingForKey()
{
mWaitingForKeyEvent.Notify();
}
MediaEventSource<void>*
MediaSourceDecoder::WaitingForKeyEvent()
{
return &mWaitingForKeyEvent;
}
TimeInterval
MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mEnded) {
return aInterval;
}
TimeUnit duration = TimeUnit::FromSeconds(GetDuration());
if (duration < aInterval.mStart) {
return aInterval;
}
return TimeInterval(aInterval.mStart,
std::min(aInterval.mEnd, duration),
aInterval.mFuzz);
}
#undef MSE_DEBUG
#undef MSE_DEBUGV
} // namespace mozilla

View file

@ -0,0 +1,96 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_MEDIASOURCEDECODER_H_
#define MOZILLA_MEDIASOURCEDECODER_H_
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "MediaDecoder.h"
#include "MediaFormatReader.h"
class nsIStreamListener;
namespace mozilla {
class MediaResource;
class MediaDecoderStateMachine;
class SourceBufferDecoder;
class TrackBuffer;
enum MSRangeRemovalAction : uint8_t;
class MediaSourceDemuxer;
namespace dom {
class HTMLMediaElement;
class MediaSource;
} // namespace dom
class MediaSourceDecoder : public MediaDecoder
{
public:
explicit MediaSourceDecoder(dom::HTMLMediaElement* aElement);
MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
MediaDecoderStateMachine* CreateStateMachine() override;
nsresult Load(nsIStreamListener**) override;
media::TimeIntervals GetSeekable() override;
media::TimeIntervals GetBuffered() override;
void Shutdown() override;
static already_AddRefed<MediaResource> CreateResource(nsIPrincipal* aPrincipal = nullptr);
void AttachMediaSource(dom::MediaSource* aMediaSource);
void DetachMediaSource();
void Ended(bool aEnded);
// Return the duration of the video in seconds.
double GetDuration() override;
void SetInitialDuration(int64_t aDuration);
void SetMediaSourceDuration(double aDuration);
MediaSourceDemuxer* GetDemuxer()
{
return mDemuxer;
}
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString) override;
void AddSizeOfResources(ResourceSizes* aSizes) override;
MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus() override;
bool CanPlayThrough() override;
void NotifyWaitingForKey() override;
MediaEventSource<void>* WaitingForKeyEvent() override;
private:
void DoSetMediaSourceDuration(double aDuration);
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
// The owning MediaSource holds a strong reference to this decoder, and
// calls Attach/DetachMediaSource on this decoder to set and clear
// mMediaSource.
dom::MediaSource* mMediaSource;
RefPtr<MediaSourceDemuxer> mDemuxer;
RefPtr<MediaFormatReader> mReader;
MediaEventProducer<void> mWaitingForKeyEvent;
bool mEnded;
};
} // namespace mozilla
#endif /* MOZILLA_MEDIASOURCEDECODER_H_ */

View file

@ -0,0 +1,499 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 <algorithm>
#include <limits>
#include <stdint.h>
#include "MediaSourceDemuxer.h"
#include "MediaSourceUtils.h"
#include "SourceBufferList.h"
#include "nsPrintfCString.h"
#include "OpusDecoder.h"
namespace mozilla {
typedef TrackInfo::TrackType TrackType;
using media::TimeUnit;
using media::TimeIntervals;
MediaSourceDemuxer::MediaSourceDemuxer()
: mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ false))
, mMonitor("MediaSourceDemuxer")
{
MOZ_ASSERT(NS_IsMainThread());
}
// Due to inaccuracies in determining buffer end
// frames (Bug 1065207). This value is based on videos seen in the wild.
const TimeUnit MediaSourceDemuxer::EOS_FUZZ = media::TimeUnit::FromMicroseconds(500000);
RefPtr<MediaSourceDemuxer::InitPromise>
MediaSourceDemuxer::Init()
{
return InvokeAsync(GetTaskQueue(), this, __func__,
&MediaSourceDemuxer::AttemptInit);
}
RefPtr<MediaSourceDemuxer::InitPromise>
MediaSourceDemuxer::AttemptInit()
{
MOZ_ASSERT(OnTaskQueue());
if (ScanSourceBuffersForContent()) {
return InitPromise::CreateAndResolve(NS_OK, __func__);
}
RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
return p;
}
void
MediaSourceDemuxer::AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes)
{
MOZ_ASSERT(NS_IsMainThread());
// NB: The track buffers must only be accessed on the TaskQueue.
RefPtr<MediaSourceDemuxer> self = this;
RefPtr<MediaSourceDecoder::ResourceSizes> sizes = aSizes;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self, sizes] () {
for (TrackBuffersManager* manager : self->mSourceBuffers) {
manager->AddSizeOfResources(sizes);
}
});
GetTaskQueue()->Dispatch(task.forget());
}
void MediaSourceDemuxer::NotifyDataArrived()
{
RefPtr<MediaSourceDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self] () {
if (self->mInitPromise.IsEmpty()) {
return;
}
if (self->ScanSourceBuffersForContent()) {
self->mInitPromise.ResolveIfExists(NS_OK, __func__);
}
});
GetTaskQueue()->Dispatch(task.forget());
}
bool
MediaSourceDemuxer::ScanSourceBuffersForContent()
{
MOZ_ASSERT(OnTaskQueue());
if (mSourceBuffers.IsEmpty()) {
return false;
}
MonitorAutoLock mon(mMonitor);
bool haveEmptySourceBuffer = false;
for (const auto& sourceBuffer : mSourceBuffers) {
MediaInfo info = sourceBuffer->GetMetadata();
if (!info.HasAudio() && !info.HasVideo()) {
haveEmptySourceBuffer = true;
}
if (info.HasAudio() && !mAudioTrack) {
mInfo.mAudio = info.mAudio;
mAudioTrack = sourceBuffer;
}
if (info.HasVideo() && !mVideoTrack) {
mInfo.mVideo = info.mVideo;
mVideoTrack = sourceBuffer;
}
if (info.IsEncrypted() && !mInfo.IsEncrypted()) {
mInfo.mCrypto = info.mCrypto;
}
}
if (mInfo.HasAudio() && mInfo.HasVideo()) {
// We have both audio and video. We can ignore non-ready source buffer.
return true;
}
return !haveEmptySourceBuffer;
}
bool
MediaSourceDemuxer::HasTrackType(TrackType aType) const
{
MonitorAutoLock mon(mMonitor);
switch (aType) {
case TrackType::kAudioTrack:
return mInfo.HasAudio();
case TrackType::kVideoTrack:
return mInfo.HasVideo();
default:
return false;
}
}
uint32_t
MediaSourceDemuxer::GetNumberTracks(TrackType aType) const
{
return HasTrackType(aType) ? 1u : 0;
}
already_AddRefed<MediaTrackDemuxer>
MediaSourceDemuxer::GetTrackDemuxer(TrackType aType, uint32_t aTrackNumber)
{
RefPtr<TrackBuffersManager> manager = GetManager(aType);
if (!manager) {
return nullptr;
}
RefPtr<MediaSourceTrackDemuxer> e =
new MediaSourceTrackDemuxer(this, aType, manager);
mDemuxers.AppendElement(e);
return e.forget();
}
bool
MediaSourceDemuxer::IsSeekable() const
{
return true;
}
UniquePtr<EncryptionInfo>
MediaSourceDemuxer::GetCrypto()
{
MonitorAutoLock mon(mMonitor);
auto crypto = MakeUnique<EncryptionInfo>();
*crypto = mInfo.mCrypto;
return crypto;
}
void
MediaSourceDemuxer::AttachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
nsCOMPtr<nsIRunnable> task =
NewRunnableMethod<TrackBuffersManager*>(
this, &MediaSourceDemuxer::DoAttachSourceBuffer,
aSourceBuffer);
GetTaskQueue()->Dispatch(task.forget());
}
void
MediaSourceDemuxer::DoAttachSourceBuffer(mozilla::TrackBuffersManager* aSourceBuffer)
{
MOZ_ASSERT(OnTaskQueue());
mSourceBuffers.AppendElement(aSourceBuffer);
ScanSourceBuffersForContent();
}
void
MediaSourceDemuxer::DetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
nsCOMPtr<nsIRunnable> task =
NewRunnableMethod<TrackBuffersManager*>(
this, &MediaSourceDemuxer::DoDetachSourceBuffer,
aSourceBuffer);
GetTaskQueue()->Dispatch(task.forget());
}
void
MediaSourceDemuxer::DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
MOZ_ASSERT(OnTaskQueue());
for (uint32_t i = 0; i < mSourceBuffers.Length(); i++) {
if (mSourceBuffers[i].get() == aSourceBuffer) {
mSourceBuffers.RemoveElementAt(i);
}
}
if (aSourceBuffer == mAudioTrack) {
mAudioTrack = nullptr;
}
if (aSourceBuffer == mVideoTrack) {
mVideoTrack = nullptr;
}
ScanSourceBuffersForContent();
}
TrackInfo*
MediaSourceDemuxer::GetTrackInfo(TrackType aTrack)
{
MonitorAutoLock mon(mMonitor);
switch (aTrack) {
case TrackType::kAudioTrack:
return &mInfo.mAudio;
case TrackType::kVideoTrack:
return &mInfo.mVideo;
default:
return nullptr;
}
}
TrackBuffersManager*
MediaSourceDemuxer::GetManager(TrackType aTrack)
{
MonitorAutoLock mon(mMonitor);
switch (aTrack) {
case TrackType::kAudioTrack:
return mAudioTrack;
case TrackType::kVideoTrack:
return mVideoTrack;
default:
return nullptr;
}
}
MediaSourceDemuxer::~MediaSourceDemuxer()
{
mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
void
MediaSourceDemuxer::GetMozDebugReaderData(nsAString& aString)
{
MonitorAutoLock mon(mMonitor);
nsAutoCString result;
result += nsPrintfCString("Dumping data for demuxer %p:\n", this);
if (mAudioTrack) {
result += nsPrintfCString("\tDumping Audio Track Buffer(%s): - mLastAudioTime: %f\n"
"\t\tNumSamples:%u Size:%u Evictable:%u NextGetSampleIndex:%u NextInsertionIndex:%d\n",
mAudioTrack->mAudioTracks.mInfo->mMimeType.get(),
mAudioTrack->mAudioTracks.mNextSampleTime.ToSeconds(),
mAudioTrack->mAudioTracks.mBuffers[0].Length(),
mAudioTrack->mAudioTracks.mSizeBuffer,
mAudioTrack->Evictable(TrackInfo::kAudioTrack),
mAudioTrack->mAudioTracks.mNextGetSampleIndex.valueOr(-1),
mAudioTrack->mAudioTracks.mNextInsertionIndex.valueOr(-1));
result += nsPrintfCString("\t\tBuffered: ranges=%s\n",
DumpTimeRanges(mAudioTrack->SafeBuffered(TrackInfo::kAudioTrack)).get());
}
if (mVideoTrack) {
result += nsPrintfCString("\tDumping Video Track Buffer(%s) - mLastVideoTime: %f\n"
"\t\tNumSamples:%u Size:%u Evictable:%u NextGetSampleIndex:%u NextInsertionIndex:%d\n",
mVideoTrack->mVideoTracks.mInfo->mMimeType.get(),
mVideoTrack->mVideoTracks.mNextSampleTime.ToSeconds(),
mVideoTrack->mVideoTracks.mBuffers[0].Length(),
mVideoTrack->mVideoTracks.mSizeBuffer,
mVideoTrack->Evictable(TrackInfo::kVideoTrack),
mVideoTrack->mVideoTracks.mNextGetSampleIndex.valueOr(-1),
mVideoTrack->mVideoTracks.mNextInsertionIndex.valueOr(-1));
result += nsPrintfCString("\t\tBuffered: ranges=%s\n",
DumpTimeRanges(mVideoTrack->SafeBuffered(TrackInfo::kVideoTrack)).get());
}
aString += NS_ConvertUTF8toUTF16(result);
}
MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
TrackInfo::TrackType aType,
TrackBuffersManager* aManager)
: mParent(aParent)
, mManager(aManager)
, mType(aType)
, mMonitor("MediaSourceTrackDemuxer")
, mReset(true)
, mPreRoll(
TimeUnit::FromMicroseconds(
OpusDataDecoder::IsOpus(mParent->GetTrackInfo(mType)->mMimeType)
? 80000 : 0))
{
}
UniquePtr<TrackInfo>
MediaSourceTrackDemuxer::GetInfo() const
{
return mParent->GetTrackInfo(mType)->Clone();
}
RefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::Seek(media::TimeUnit aTime)
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSeek, aTime);
}
RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
MediaSourceTrackDemuxer::GetSamples(int32_t aNumSamples)
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoGetSamples, aNumSamples);
}
void
MediaSourceTrackDemuxer::Reset()
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
RefPtr<MediaSourceTrackDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self] () {
self->mNextSample.reset();
self->mReset = true;
self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
{
MonitorAutoLock mon(self->mMonitor);
self->mNextRandomAccessPoint =
self->mManager->GetNextRandomAccessPoint(self->mType,
MediaSourceDemuxer::EOS_FUZZ);
}
});
mParent->GetTaskQueue()->Dispatch(task.forget());
}
nsresult
MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
{
MonitorAutoLock mon(mMonitor);
*aTime = mNextRandomAccessPoint;
return NS_OK;
}
RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
{
return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint,
aTimeThreshold);
}
media::TimeIntervals
MediaSourceTrackDemuxer::GetBuffered()
{
return mManager->Buffered();
}
void
MediaSourceTrackDemuxer::BreakCycles()
{
RefPtr<MediaSourceTrackDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self]() {
self->mParent = nullptr;
self->mManager = nullptr;
} );
mParent->GetTaskQueue()->Dispatch(task.forget());
}
RefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
{
TimeIntervals buffered = mManager->Buffered(mType);
// Fuzz factor represents a +/- threshold. So when seeking it allows the gap
// to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap.
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::FromMicroseconds(0));
if (mManager->IsEnded() && seekTime >= buffered.GetEnd()) {
// We're attempting to seek past the end time. Cap seekTime so that we seek
// to the last sample instead.
seekTime =
std::max(mManager->HighestStartTime(mType) - mPreRoll,
TimeUnit::FromMicroseconds(0));
}
if (!buffered.ContainsWithStrictEnd(seekTime)) {
if (!buffered.ContainsWithStrictEnd(aTime)) {
// We don't have the data to seek to.
return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
__func__);
}
// Theoretically we should reject the promise with WAITING_FOR_DATA,
// however, to avoid unwanted regressions we assume that if at this time
// we don't have the wanted data it won't come later.
// Instead of using the pre-rolled time, use the earliest time available in
// the interval.
TimeIntervals::IndexType index = buffered.Find(aTime);
MOZ_ASSERT(index != TimeIntervals::NoIndex);
seekTime = buffered[index].mStart;
}
seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ);
MediaResult result = NS_OK;
RefPtr<MediaRawData> sample =
mManager->GetSample(mType,
media::TimeUnit(),
result);
MOZ_ASSERT(NS_SUCCEEDED(result) && sample);
mNextSample = Some(sample);
mReset = false;
{
MonitorAutoLock mon(mMonitor);
mNextRandomAccessPoint =
mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
}
return SeekPromise::CreateAndResolve(seekTime, __func__);
}
RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
{
if (mReset) {
// If a seek (or reset) was recently performed, we ensure that the data
// we are about to retrieve is still available.
TimeIntervals buffered = mManager->Buffered(mType);
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
if (!buffered.Length() && mManager->IsEnded()) {
return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
__func__);
}
if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) {
return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
__func__);
}
mReset = false;
}
RefPtr<MediaRawData> sample;
if (mNextSample) {
sample = mNextSample.ref();
mNextSample.reset();
} else {
MediaResult result = NS_OK;
sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, result);
if (!sample) {
if (result == NS_ERROR_DOM_MEDIA_END_OF_STREAM ||
result == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
return SamplesPromise::CreateAndReject(
(result == NS_ERROR_DOM_MEDIA_END_OF_STREAM && mManager->IsEnded())
? NS_ERROR_DOM_MEDIA_END_OF_STREAM
: NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
}
return SamplesPromise::CreateAndReject(result, __func__);
}
}
RefPtr<SamplesHolder> samples = new SamplesHolder;
samples->mSamples.AppendElement(sample);
if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
MonitorAutoLock mon(mMonitor);
mNextRandomAccessPoint =
mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
}
return SamplesPromise::CreateAndResolve(samples, __func__);
}
RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold)
{
uint32_t parsed = 0;
// Ensure that the data we are about to skip to is still available.
TimeIntervals buffered = mManager->Buffered(mType);
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
if (buffered.ContainsWithStrictEnd(aTimeThreadshold)) {
bool found;
parsed = mManager->SkipToNextRandomAccessPoint(mType,
aTimeThreadshold,
MediaSourceDemuxer::EOS_FUZZ,
found);
if (found) {
return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
}
}
SkipFailureHolder holder(
mManager->IsEnded() ? NS_ERROR_DOM_MEDIA_END_OF_STREAM :
NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, parsed);
return SkipAccessPointPromise::CreateAndReject(holder, __func__);
}
} // namespace mozilla

View file

@ -0,0 +1,145 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(MediaSourceDemuxer_h_)
#define MediaSourceDemuxer_h_
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "AutoTaskQueue.h"
#include "MediaDataDemuxer.h"
#include "MediaDecoderReader.h"
#include "MediaResource.h"
#include "MediaSource.h"
#include "TrackBuffersManager.h"
namespace mozilla {
class MediaResult;
class MediaSourceTrackDemuxer;
class MediaSourceDemuxer : public MediaDataDemuxer
{
public:
explicit MediaSourceDemuxer();
RefPtr<InitPromise> Init() override;
bool HasTrackType(TrackInfo::TrackType aType) const override;
uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
uint32_t aTrackNumber) override;
bool IsSeekable() const override;
UniquePtr<EncryptionInfo> GetCrypto() override;
bool ShouldComputeStartTime() const override { return false; }
void NotifyDataArrived() override;
/* interface for TrackBuffersManager */
void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
AutoTaskQueue* GetTaskQueue() { return mTaskQueue; }
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
// Gap allowed between frames.
static const media::TimeUnit EOS_FUZZ;
private:
~MediaSourceDemuxer();
friend class MediaSourceTrackDemuxer;
// Scan source buffers and update information.
bool ScanSourceBuffersForContent();
RefPtr<InitPromise> AttemptInit();
TrackBuffersManager* GetManager(TrackInfo::TrackType aType);
TrackInfo* GetTrackInfo(TrackInfo::TrackType);
void DoAttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
void DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
bool OnTaskQueue()
{
return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
}
RefPtr<AutoTaskQueue> mTaskQueue;
nsTArray<RefPtr<MediaSourceTrackDemuxer>> mDemuxers;
nsTArray<RefPtr<TrackBuffersManager>> mSourceBuffers;
MozPromiseHolder<InitPromise> mInitPromise;
// Monitor to protect members below across multiple threads.
mutable Monitor mMonitor;
RefPtr<TrackBuffersManager> mAudioTrack;
RefPtr<TrackBuffersManager> mVideoTrack;
MediaInfo mInfo;
};
class MediaSourceTrackDemuxer : public MediaTrackDemuxer
{
public:
MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
TrackInfo::TrackType aType,
TrackBuffersManager* aManager);
UniquePtr<TrackInfo> GetInfo() const override;
RefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
void Reset() override;
nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
media::TimeIntervals GetBuffered() override;
void BreakCycles() override;
bool GetSamplesMayBlock() const override
{
return false;
}
private:
RefPtr<SeekPromise> DoSeek(media::TimeUnit aTime);
RefPtr<SamplesPromise> DoGetSamples(int32_t aNumSamples);
RefPtr<SkipAccessPointPromise> DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold);
already_AddRefed<MediaRawData> GetSample(MediaResult& aError);
// Return the timestamp of the next keyframe after mLastSampleIndex.
media::TimeUnit GetNextRandomAccessPoint();
RefPtr<MediaSourceDemuxer> mParent;
RefPtr<TrackBuffersManager> mManager;
TrackInfo::TrackType mType;
// Monitor protecting members below accessed from multiple threads.
Monitor mMonitor;
media::TimeUnit mNextRandomAccessPoint;
Maybe<RefPtr<MediaRawData>> mNextSample;
// Set to true following a reset. Ensure that the next sample demuxed
// is available at position 0.
bool mReset;
// Amount of pre-roll time when seeking.
// Set to 80ms if track is Opus.
const media::TimeUnit mPreRoll;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,109 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_MEDIASOURCERESOURCE_H_
#define MOZILLA_MEDIASOURCERESOURCE_H_
#include "MediaResource.h"
#include "mozilla/Monitor.h"
#include "mozilla/Logging.h"
extern mozilla::LogModule* GetMediaSourceLog();
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define UNIMPLEMENTED() MSE_DEBUG("UNIMPLEMENTED FUNCTION at %s:%d", __FILE__, __LINE__)
namespace mozilla {
class MediaSourceResource final : public MediaResource
{
public:
explicit MediaSourceResource(nsIPrincipal* aPrincipal = nullptr)
: mPrincipal(aPrincipal)
, mMonitor("MediaSourceResource")
, mEnded(false)
{}
nsresult Close() override { return NS_OK; }
void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
void Resume() override { UNIMPLEMENTED(); }
bool CanClone() override { UNIMPLEMENTED(); return false; }
already_AddRefed<MediaResource> CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; }
void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
int64_t Tell() override { UNIMPLEMENTED(); return -1; }
void Pin() override { UNIMPLEMENTED(); }
void Unpin() override { UNIMPLEMENTED(); }
double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
int64_t GetLength() override { UNIMPLEMENTED(); return -1; }
int64_t GetNextCachedData(int64_t aOffset) override { UNIMPLEMENTED(); return -1; }
int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; }
bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; }
bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; }
bool IsSuspended() override { UNIMPLEMENTED(); return false; }
nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
{
return RefPtr<nsIPrincipal>(mPrincipal).forget();
}
nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override
{
UNIMPLEMENTED();
aRanges += MediaByteRange(0, GetLength());
return NS_OK;
}
bool IsTransportSeekable() override { return true; }
const nsCString& GetContentType() const override { return mType; }
bool IsLiveStream() override
{
MonitorAutoLock mon(mMonitor);
return !mEnded;
}
void SetEnded(bool aEnded)
{
MonitorAutoLock mon(mMonitor);
mEnded = aEnded;
}
bool IsExpectingMoreData() override
{
MonitorAutoLock mon(mMonitor);
return !mEnded;
}
private:
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
{
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
size += mType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
return size;
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
RefPtr<nsIPrincipal> mPrincipal;
const nsCString mType;
Monitor mMonitor;
bool mEnded; // protected by mMonitor
};
} // namespace mozilla
#undef MSE_DEBUG
#undef UNIMPLEMENTED
#endif /* MOZILLA_MEDIASOURCERESOURCE_H_ */

View file

@ -0,0 +1,34 @@
/* -*- mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "MediaSourceUtils.h"
#include "mozilla/Logging.h"
#include "nsPrintfCString.h"
namespace mozilla {
nsCString
DumpTimeRanges(const media::TimeIntervals& aRanges)
{
nsCString dump;
dump = "[";
for (uint32_t i = 0; i < aRanges.Length(); ++i) {
if (i > 0) {
dump += ", ";
}
dump += nsPrintfCString("(%f, %f)",
aRanges.Start(i).ToSeconds(),
aRanges.End(i).ToSeconds());
}
dump += "]";
return dump;
}
} // namespace mozilla

View file

@ -0,0 +1,19 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_MEDIASOURCEUTILS_H_
#define MOZILLA_MEDIASOURCEUTILS_H_
#include "nsString.h"
#include "TimeUnits.h"
namespace mozilla {
nsCString DumpTimeRanges(const media::TimeIntervals& aRanges);
} // namespace mozilla
#endif /* MOZILLA_MEDIASOURCEUTILS_H_ */

View file

@ -0,0 +1,214 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ResourceQueue.h"
#include "nsDeque.h"
#include "MediaData.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Logging.h"
#include "mozilla/Sprintf.h"
extern mozilla::LogModule* GetSourceBufferResourceLog();
#define SBR_DEBUG(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define SBR_DEBUGV(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
namespace mozilla {
ResourceItem::ResourceItem(MediaByteBuffer* aData)
: mData(aData)
{
}
size_t
ResourceItem::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
// size including this
size_t size = aMallocSizeOf(this);
// size excluding this
size += mData->ShallowSizeOfExcludingThis(aMallocSizeOf);
return size;
}
class ResourceQueueDeallocator : public nsDequeFunctor {
void* operator() (void* aObject) override {
delete static_cast<ResourceItem*>(aObject);
return nullptr;
}
};
ResourceQueue::ResourceQueue()
: nsDeque(new ResourceQueueDeallocator())
, mLogicalLength(0)
, mOffset(0)
{
}
uint64_t
ResourceQueue::GetOffset()
{
return mOffset;
}
uint64_t
ResourceQueue::GetLength()
{
return mLogicalLength;
}
void
ResourceQueue::CopyData(uint64_t aOffset, uint32_t aCount, char* aDest)
{
uint32_t offset = 0;
uint32_t start = GetAtOffset(aOffset, &offset);
uint32_t end = std::min(GetAtOffset(aOffset + aCount, nullptr) + 1, uint32_t(GetSize()));
for (uint32_t i = start; i < end; ++i) {
ResourceItem* item = ResourceAt(i);
uint32_t bytes = std::min(aCount, uint32_t(item->mData->Length() - offset));
if (bytes != 0) {
memcpy(aDest, &(*item->mData)[offset], bytes);
offset = 0;
aCount -= bytes;
aDest += bytes;
}
}
}
void
ResourceQueue::AppendItem(MediaByteBuffer* aData)
{
mLogicalLength += aData->Length();
Push(new ResourceItem(aData));
}
uint32_t
ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict,
ErrorResult& aRv)
{
SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)",
aOffset, aSizeToEvict);
return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict), aRv);
}
uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
{
SBR_DEBUG("EvictBefore(%llu)", aOffset);
uint32_t evicted = 0;
while (ResourceItem* item = ResourceAt(0)) {
SBR_DEBUG("item=%p length=%d offset=%llu",
item, item->mData->Length(), mOffset);
if (item->mData->Length() + mOffset >= aOffset) {
if (aOffset <= mOffset) {
break;
}
uint32_t offset = aOffset - mOffset;
mOffset += offset;
evicted += offset;
RefPtr<MediaByteBuffer> data = new MediaByteBuffer;
if (!data->AppendElements(item->mData->Elements() + offset,
item->mData->Length() - offset,
fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return 0;
}
item->mData = data;
break;
}
mOffset += item->mData->Length();
evicted += item->mData->Length();
delete PopFront();
}
return evicted;
}
uint32_t
ResourceQueue::EvictAll()
{
SBR_DEBUG("EvictAll()");
uint32_t evicted = 0;
while (ResourceItem* item = ResourceAt(0)) {
SBR_DEBUG("item=%p length=%d offset=%llu",
item, item->mData->Length(), mOffset);
mOffset += item->mData->Length();
evicted += item->mData->Length();
delete PopFront();
}
return evicted;
}
size_t
ResourceQueue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
// Calculate the size of the internal deque.
size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
// Sum the ResourceItems.
for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
const ResourceItem* item = ResourceAt(i);
size += item->SizeOfIncludingThis(aMallocSizeOf);
}
return size;
}
#if defined(DEBUG)
void
ResourceQueue::Dump(const char* aPath)
{
for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
ResourceItem* item = ResourceAt(i);
char buf[255];
SprintfLiteral(buf, "%s/%08u.bin", aPath, i);
FILE* fp = fopen(buf, "wb");
if (!fp) {
return;
}
fwrite(item->mData->Elements(), item->mData->Length(), 1, fp);
fclose(fp);
}
}
#endif
ResourceItem*
ResourceQueue::ResourceAt(uint32_t aIndex) const
{
return static_cast<ResourceItem*>(ObjectAt(aIndex));
}
uint32_t
ResourceQueue::GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset)
{
MOZ_RELEASE_ASSERT(aOffset >= mOffset);
uint64_t offset = mOffset;
for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
ResourceItem* item = ResourceAt(i);
// If the item contains the start of the offset we want to
// break out of the loop.
if (item->mData->Length() + offset > aOffset) {
if (aResourceOffset) {
*aResourceOffset = aOffset - offset;
}
return i;
}
offset += item->mData->Length();
}
return GetSize();
}
ResourceItem*
ResourceQueue::PopFront()
{
return static_cast<ResourceItem*>(nsDeque::PopFront());
}
#undef SBR_DEBUG
#undef SBR_DEBUGV
} // namespace mozilla

View file

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_RESOURCEQUEUE_H_
#define MOZILLA_RESOURCEQUEUE_H_
#include "nsDeque.h"
#include "MediaData.h"
namespace mozilla {
class ErrorResult;
// A SourceBufferResource has a queue containing the data that is appended
// to it. The queue holds instances of ResourceItem which is an array of the
// bytes. Appending data to the SourceBufferResource pushes this onto the
// queue.
// Data is evicted once it reaches a size threshold. This pops the items off
// the front of the queue and deletes it. If an eviction happens then the
// MediaSource is notified (done in SourceBuffer::AppendData) which then
// requests all SourceBuffers to evict data up to approximately the same
// timepoint.
struct ResourceItem {
explicit ResourceItem(MediaByteBuffer* aData);
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
RefPtr<MediaByteBuffer> mData;
};
class ResourceQueue : private nsDeque {
public:
ResourceQueue();
// Returns the logical byte offset of the start of the data.
uint64_t GetOffset();
// Returns the length of all items in the queue plus the offset.
// This is the logical length of the resource.
uint64_t GetLength();
// Copies aCount bytes from aOffset in the queue into aDest.
void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest);
void AppendItem(MediaByteBuffer* aData);
// Tries to evict at least aSizeToEvict from the queue up until
// aOffset. Returns amount evicted.
uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict,
ErrorResult& aRv);
uint32_t EvictBefore(uint64_t aOffset, ErrorResult& aRv);
uint32_t EvictAll();
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
#if defined(DEBUG)
void Dump(const char* aPath);
#endif
private:
ResourceItem* ResourceAt(uint32_t aIndex) const;
// Returns the index of the resource that contains the given
// logical offset. aResourceOffset will contain the offset into
// the resource at the given index returned if it is not null. If
// no such resource exists, returns GetSize() and aOffset is
// untouched.
uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset);
ResourceItem* PopFront();
// Logical length of the resource.
uint64_t mLogicalLength;
// Logical offset into the resource of the first element in the queue.
uint64_t mOffset;
};
} // namespace mozilla
#endif /* MOZILLA_RESOURCEQUEUE_H_ */

View file

@ -0,0 +1,596 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "SourceBuffer.h"
#include "AsyncEventRunner.h"
#include "MediaData.h"
#include "MediaSourceDemuxer.h"
#include "MediaSourceUtils.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/MediaSourceBinding.h"
#include "mozilla/dom/TimeRanges.h"
#include "nsError.h"
#include "nsIEventTarget.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
#include "mozilla/Logging.h"
#include <time.h>
#include "TimeUnits.h"
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with MediaDecoder::GetCurrentTime implementation.
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
struct JSContext;
class JSObject;
extern mozilla::LogModule* GetMediaSourceLog();
extern mozilla::LogModule* GetMediaSourceAPILog();
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
namespace mozilla {
using media::TimeUnit;
typedef SourceBufferAttributes::AppendState AppendState;
namespace dom {
void
SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetMode(aMode=%d)", aMode);
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mCurrentAttributes.mGenerateTimestamps &&
aMode == SourceBufferAppendMode::Segments) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (aMode == SourceBufferAppendMode::Sequence) {
// Will set GroupStartTimestamp to GroupEndTimestamp.
mCurrentAttributes.RestartGroupStartTimestamp();
}
mCurrentAttributes.SetAppendMode(aMode);
}
void
SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetTimestampOffset(aTimestampOffset=%f)", aTimestampOffset);
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mCurrentAttributes.SetApparentTimestampOffset(aTimestampOffset);
if (mCurrentAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) {
mCurrentAttributes.SetGroupStartTimestamp(mCurrentAttributes.GetTimestampOffset());
}
}
TimeRanges*
SourceBuffer::GetBuffered(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
// http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
// 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an InvalidStateError exception and abort these steps.
if (!IsAttached()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
bool rangeChanged = true;
media::TimeIntervals intersection = mTrackBuffersManager->Buffered();
MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get());
if (mBuffered) {
media::TimeIntervals currentValue(mBuffered);
rangeChanged = (intersection != currentValue);
MSE_DEBUGV("currentValue=%s", DumpTimeRanges(currentValue).get());
}
// 5. If intersection ranges does not contain the exact same range information as the current value of this attribute, then update the current value of this attribute to intersection ranges.
if (rangeChanged) {
mBuffered = new TimeRanges(ToSupports(this));
intersection.ToTimeRanges(mBuffered);
}
// 6. Return the current value of this attribute.
return mBuffered;
}
media::TimeIntervals
SourceBuffer::GetTimeIntervals()
{
return mTrackBuffersManager->Buffered();
}
void
SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetAppendWindowStart(aAppendWindowStart=%f)", aAppendWindowStart);
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (aAppendWindowStart < 0 ||
aAppendWindowStart >= mCurrentAttributes.GetAppendWindowEnd()) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
mCurrentAttributes.SetAppendWindowStart(aAppendWindowStart);
}
void
SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetAppendWindowEnd(aAppendWindowEnd=%f)", aAppendWindowEnd);
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (IsNaN(aAppendWindowEnd) ||
aAppendWindowEnd <= mCurrentAttributes.GetAppendWindowStart()) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
mCurrentAttributes.SetAppendWindowEnd(aAppendWindowEnd);
}
void
SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBuffer(ArrayBuffer)");
aData.ComputeLengthAndData();
AppendData(aData.Data(), aData.Length(), aRv);
}
void
SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBuffer(ArrayBufferView)");
aData.ComputeLengthAndData();
AppendData(aData.Data(), aData.Length(), aRv);
}
void
SourceBuffer::Abort(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("Abort()");
if (!IsAttached()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mPendingRemoval.Exists()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
AbortBufferAppend();
ResetParserState();
mCurrentAttributes.SetAppendWindowStart(0);
mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>());
}
void
SourceBuffer::AbortBufferAppend()
{
if (mUpdating) {
if (mPendingAppend.Exists()) {
mPendingAppend.Disconnect();
mTrackBuffersManager->AbortAppendData();
}
AbortUpdating();
}
}
void
SourceBuffer::ResetParserState()
{
mTrackBuffersManager->ResetParserState(mCurrentAttributes);
}
void
SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("Remove(aStart=%f, aEnd=%f)", aStart, aEnd);
if (!IsAttached()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (IsNaN(mMediaSource->Duration()) ||
aStart < 0 || aStart > mMediaSource->Duration() ||
aEnd <= aStart || IsNaN(aEnd)) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
RangeRemoval(aStart, aEnd);
}
void
SourceBuffer::RangeRemoval(double aStart, double aEnd)
{
StartUpdating();
RefPtr<SourceBuffer> self = this;
mPendingRemoval.Begin(
mTrackBuffersManager->RangeRemoval(TimeUnit::FromSeconds(aStart),
TimeUnit::FromSeconds(aEnd))
->Then(AbstractThread::MainThread(), __func__,
[self] (bool) {
self->mPendingRemoval.Complete();
self->StopUpdating();
},
[]() { MOZ_ASSERT(false); }));
}
void
SourceBuffer::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Detach");
if (!mMediaSource) {
MSE_DEBUG("Already detached");
return;
}
AbortBufferAppend();
if (mTrackBuffersManager) {
mTrackBuffersManager->Detach();
mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer(
mTrackBuffersManager.get());
}
mTrackBuffersManager = nullptr;
mMediaSource = nullptr;
}
void
SourceBuffer::Ended()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsAttached());
MSE_DEBUG("Ended");
mTrackBuffersManager->Ended();
}
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
: DOMEventTargetHelper(aMediaSource->GetParentObject())
, mMediaSource(aMediaSource)
, mCurrentAttributes(aType.LowerCaseEqualsLiteral("audio/mpeg") ||
aType.LowerCaseEqualsLiteral("audio/aac"))
, mUpdating(false)
, mActive(false)
, mType(aType)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aMediaSource);
mTrackBuffersManager =
new TrackBuffersManager(aMediaSource->GetDecoder(), aType);
MSE_DEBUG("Create mTrackBuffersManager=%p",
mTrackBuffersManager.get());
ErrorResult dummy;
if (mCurrentAttributes.mGenerateTimestamps) {
SetMode(SourceBufferAppendMode::Sequence, dummy);
} else {
SetMode(SourceBufferAppendMode::Segments, dummy);
}
mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer(
mTrackBuffersManager.get());
}
SourceBuffer::~SourceBuffer()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mMediaSource);
MSE_DEBUG("");
}
MediaSource*
SourceBuffer::GetParentObject() const
{
return mMediaSource;
}
JSObject*
SourceBuffer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return SourceBufferBinding::Wrap(aCx, this, aGivenProto);
}
void
SourceBuffer::DispatchSimpleEvent(const char* aName)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("Dispatch event '%s'", aName);
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
}
void
SourceBuffer::QueueAsyncSimpleEvent(const char* aName)
{
MSE_DEBUG("Queuing event '%s'", aName);
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
void
SourceBuffer::StartUpdating()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mUpdating);
mUpdating = true;
QueueAsyncSimpleEvent("updatestart");
}
void
SourceBuffer::StopUpdating()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mUpdating) {
// The buffer append or range removal algorithm has been interrupted by
// abort().
return;
}
mUpdating = false;
QueueAsyncSimpleEvent("update");
QueueAsyncSimpleEvent("updateend");
}
void
SourceBuffer::AbortUpdating()
{
MOZ_ASSERT(NS_IsMainThread());
mUpdating = false;
QueueAsyncSimpleEvent("abort");
QueueAsyncSimpleEvent("updateend");
}
void
SourceBuffer::CheckEndTime()
{
MOZ_ASSERT(NS_IsMainThread());
// Check if we need to update mMediaSource duration
double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds();
double duration = mMediaSource->Duration();
if (endTime > duration) {
mMediaSource->SetDuration(endTime);
}
}
void
SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
{
MSE_DEBUG("AppendData(aLength=%u)", aLength);
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv);
if (!data) {
return;
}
StartUpdating();
mPendingAppend.Begin(mTrackBuffersManager->AppendData(data, mCurrentAttributes)
->Then(AbstractThread::MainThread(), __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored));
}
void
SourceBuffer::AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult)
{
MOZ_ASSERT(mUpdating);
mPendingAppend.Complete();
if (aResult.first()) {
if (!mActive) {
mActive = true;
mMediaSource->SourceBufferIsActive(this);
}
}
if (mActive) {
// Tell our parent decoder that we have received new data.
mMediaSource->GetDecoder()->NotifyDataArrived();
// Send progress event.
mMediaSource->GetDecoder()->NotifyBytesDownloaded();
}
mCurrentAttributes = aResult.second();
CheckEndTime();
StopUpdating();
}
void
SourceBuffer::AppendDataErrored(const MediaResult& aError)
{
MOZ_ASSERT(mUpdating);
mPendingAppend.Complete();
switch (aError.Code()) {
case NS_ERROR_DOM_MEDIA_CANCELED:
// Nothing further to do as the trackbuffer has been shutdown.
// or append was aborted and abort() has handled all the events.
break;
default:
AppendError(aError);
break;
}
}
void
SourceBuffer::AppendError(const MediaResult& aDecodeError)
{
MOZ_ASSERT(NS_IsMainThread());
ResetParserState();
mUpdating = false;
QueueAsyncSimpleEvent("error");
QueueAsyncSimpleEvent("updateend");
MOZ_ASSERT(NS_FAILED(aDecodeError));
mMediaSource->EndOfStream(aDecodeError);
}
already_AddRefed<MediaByteBuffer>
SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
{
typedef TrackBuffersManager::EvictDataResult Result;
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
// If the HTMLMediaElement.error attribute is not null, then throw an
// InvalidStateError exception and abort these steps.
if (!mMediaSource->GetDecoder() ||
mMediaSource->GetDecoder()->OwnerHasError()) {
MSE_DEBUG("HTMLMediaElement.error is not null");
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
// Eviction uses a byte threshold. If the buffer is greater than the
// number of bytes then data is evicted.
// TODO: Drive evictions off memory pressure notifications.
// TODO: Consider a global eviction threshold rather than per TrackBuffer.
// Give a chance to the TrackBuffersManager to evict some data if needed.
Result evicted =
mTrackBuffersManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()),
aLength);
// See if we have enough free space to append our new data.
if (evicted == Result::BUFFER_FULL) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return nullptr;
}
RefPtr<MediaByteBuffer> data = new MediaByteBuffer();
if (!data->AppendElements(aData, aLength, fallible)) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return nullptr;
}
return data.forget();
}
double
SourceBuffer::GetBufferedStart()
{
MOZ_ASSERT(NS_IsMainThread());
ErrorResult dummy;
RefPtr<TimeRanges> ranges = GetBuffered(dummy);
return ranges->Length() > 0 ? ranges->GetStartTime() : 0;
}
double
SourceBuffer::GetBufferedEnd()
{
MOZ_ASSERT(NS_IsMainThread());
ErrorResult dummy;
RefPtr<TimeRanges> ranges = GetBuffered(dummy);
return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
}
double
SourceBuffer::HighestStartTime()
{
MOZ_ASSERT(NS_IsMainThread());
return mTrackBuffersManager
? mTrackBuffersManager->HighestStartTime().ToSeconds()
: 0.0;
}
double
SourceBuffer::HighestEndTime()
{
MOZ_ASSERT(NS_IsMainThread());
return mTrackBuffersManager
? mTrackBuffersManager->HighestEndTime().ToSeconds()
: 0.0;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer)
// Tell the TrackBuffer to end its current SourceBufferResource.
TrackBuffersManager* manager = tmp->mTrackBuffersManager;
if (manager) {
manager->Detach();
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBuffered)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SourceBuffer,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBuffered)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
#undef MSE_DEBUG
#undef MSE_DEBUGV
#undef MSE_API
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,189 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_SourceBuffer_h_
#define mozilla_dom_SourceBuffer_h_
#include "mozilla/MozPromise.h"
#include "MediaSource.h"
#include "js/RootingAPI.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/mozalloc.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nscore.h"
#include "TrackBuffersManager.h"
#include "SourceBufferTask.h"
class JSObject;
struct JSContext;
namespace mozilla {
class ErrorResult;
class MediaByteBuffer;
template <typename T> class AsyncEventRunner;
namespace dom {
class TimeRanges;
class SourceBuffer final : public DOMEventTargetHelper
{
public:
/** WebIDL Methods. */
SourceBufferAppendMode Mode() const
{
return mCurrentAttributes.GetAppendMode();
}
void SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv);
bool Updating() const
{
return mUpdating;
}
TimeRanges* GetBuffered(ErrorResult& aRv);
media::TimeIntervals GetTimeIntervals();
double TimestampOffset() const
{
return mCurrentAttributes.GetApparentTimestampOffset();
}
void SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv);
double AppendWindowStart() const
{
return mCurrentAttributes.GetAppendWindowStart();
}
void SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv);
double AppendWindowEnd() const
{
return mCurrentAttributes.GetAppendWindowEnd();
}
void SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv);
void AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv);
void AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv);
void Abort(ErrorResult& aRv);
void AbortBufferAppend();
void Remove(double aStart, double aEnd, ErrorResult& aRv);
IMPL_EVENT_HANDLER(updatestart);
IMPL_EVENT_HANDLER(update);
IMPL_EVENT_HANDLER(updateend);
IMPL_EVENT_HANDLER(error);
IMPL_EVENT_HANDLER(abort);
/** End WebIDL Methods. */
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SourceBuffer, DOMEventTargetHelper)
SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
MediaSource* GetParentObject() const;
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
// Notify the SourceBuffer that it has been detached from the
// MediaSource's sourceBuffer list.
void Detach();
bool IsAttached() const
{
return mMediaSource != nullptr;
}
void Ended();
double GetBufferedStart();
double GetBufferedEnd();
double HighestStartTime();
double HighestEndTime();
// Runs the range removal algorithm as defined by the MSE spec.
void RangeRemoval(double aStart, double aEnd);
bool IsActive() const
{
return mActive;
}
private:
~SourceBuffer();
friend class AsyncEventRunner<SourceBuffer>;
friend class BufferAppendRunnable;
friend class mozilla::TrackBuffersManager;
void DispatchSimpleEvent(const char* aName);
void QueueAsyncSimpleEvent(const char* aName);
// Update mUpdating and fire the appropriate events.
void StartUpdating();
void StopUpdating();
void AbortUpdating();
void ResetParserState();
// If the media segment contains data beyond the current duration,
// then run the duration change algorithm with new duration set to the
// maximum of the current duration and the group end timestamp.
void CheckEndTime();
// Shared implementation of AppendBuffer overloads.
void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
// Implement the "Append Error Algorithm".
// Will call endOfStream() with "decode" error if aDecodeError is true.
// 3.5.3 Append Error Algorithm
// http://w3c.github.io/media-source/#sourcebuffer-append-error
void AppendError(const MediaResult& aDecodeError);
// Implements the "Prepare Append Algorithm". Returns MediaByteBuffer object
// on success or nullptr (with aRv set) on error.
already_AddRefed<MediaByteBuffer> PrepareAppend(const uint8_t* aData,
uint32_t aLength,
ErrorResult& aRv);
void AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult);
void AppendDataErrored(const MediaResult& aError);
RefPtr<MediaSource> mMediaSource;
RefPtr<TrackBuffersManager> mTrackBuffersManager;
SourceBufferAttributes mCurrentAttributes;
bool mUpdating;
mozilla::Atomic<bool> mActive;
MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
MozPromiseRequestHolder<SourceBufferTask::RangeRemovalPromise> mPendingRemoval;
const nsCString mType;
RefPtr<TimeRanges> mBuffered;
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_SourceBuffer_h_ */

View file

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_SourceBufferAttributes_h_
#define mozilla_SourceBufferAttributes_h_
#include "TimeUnits.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "mozilla/Maybe.h"
namespace mozilla {
class SourceBufferAttributes {
public:
// Current state as per Segment Parser Loop Algorithm
// http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
enum class AppendState
{
WAITING_FOR_SEGMENT,
PARSING_INIT_SEGMENT,
PARSING_MEDIA_SEGMENT,
};
explicit SourceBufferAttributes(bool aGenerateTimestamp)
: mGenerateTimestamps(aGenerateTimestamp)
, mAppendWindowStart(0)
, mAppendWindowEnd(PositiveInfinity<double>())
, mAppendMode(dom::SourceBufferAppendMode::Segments)
, mApparentTimestampOffset(0)
, mAppendState(AppendState::WAITING_FOR_SEGMENT)
{}
SourceBufferAttributes(const SourceBufferAttributes& aOther) = default;
double GetAppendWindowStart() const
{
return mAppendWindowStart;
}
double GetAppendWindowEnd() const
{
return mAppendWindowEnd;
}
void SetAppendWindowStart(double aWindowStart)
{
mAppendWindowStart = aWindowStart;
}
void SetAppendWindowEnd(double aWindowEnd)
{
mAppendWindowEnd = aWindowEnd;
}
double GetApparentTimestampOffset() const
{
return mApparentTimestampOffset;
}
void SetApparentTimestampOffset(double aTimestampOffset)
{
mApparentTimestampOffset = aTimestampOffset;
mTimestampOffset = media::TimeUnit::FromSeconds(aTimestampOffset);
}
media::TimeUnit GetTimestampOffset() const
{
return mTimestampOffset;
}
void SetTimestampOffset(const media::TimeUnit& aTimestampOffset)
{
mTimestampOffset = aTimestampOffset;
mApparentTimestampOffset = aTimestampOffset.ToSeconds();
}
dom::SourceBufferAppendMode GetAppendMode() const
{
return mAppendMode;
}
void SetAppendMode(dom::SourceBufferAppendMode aAppendMode)
{
mAppendMode = aAppendMode;
}
void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp)
{
mGroupStartTimestamp = Some(aGroupStartTimestamp);
}
media::TimeUnit GetGroupStartTimestamp() const
{
return mGroupStartTimestamp.ref();
}
bool HaveGroupStartTimestamp() const
{
return mGroupStartTimestamp.isSome();
}
void ResetGroupStartTimestamp()
{
mGroupStartTimestamp.reset();
}
void RestartGroupStartTimestamp()
{
mGroupStartTimestamp = Some(mGroupEndTimestamp);
}
media::TimeUnit GetGroupEndTimestamp() const
{
return mGroupEndTimestamp;
}
void SetGroupEndTimestamp(const media::TimeUnit& aGroupEndTimestamp)
{
mGroupEndTimestamp = aGroupEndTimestamp;
}
AppendState GetAppendState() const
{
return mAppendState;
}
void SetAppendState(AppendState aState)
{
mAppendState = aState;
}
// mGenerateTimestamp isn't mutable once the source buffer has been constructed
bool mGenerateTimestamps;
SourceBufferAttributes& operator=(const SourceBufferAttributes& aOther) = default;
private:
SourceBufferAttributes() = delete;
double mAppendWindowStart;
double mAppendWindowEnd;
dom::SourceBufferAppendMode mAppendMode;
double mApparentTimestampOffset;
media::TimeUnit mTimestampOffset;
Maybe<media::TimeUnit> mGroupStartTimestamp;
media::TimeUnit mGroupEndTimestamp;
// The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
AppendState mAppendState;
};
} // end namespace mozilla
#endif /* mozilla_SourceBufferAttributes_h_ */

View file

@ -0,0 +1,225 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "SourceBufferList.h"
#include "AsyncEventRunner.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/SourceBufferListBinding.h"
#include "mozilla/mozalloc.h"
#include "nsCOMPtr.h"
#include "nsIRunnable.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "mozilla/Logging.h"
extern mozilla::LogModule* GetMediaSourceLog();
extern mozilla::LogModule* GetMediaSourceAPILog();
#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBufferList(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBufferList(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
struct JSContext;
class JSObject;
namespace mozilla {
namespace dom {
SourceBufferList::~SourceBufferList()
{
}
SourceBuffer*
SourceBufferList::IndexedGetter(uint32_t aIndex, bool& aFound)
{
MOZ_ASSERT(NS_IsMainThread());
aFound = aIndex < mSourceBuffers.Length();
if (!aFound) {
return nullptr;
}
return mSourceBuffers[aIndex];
}
uint32_t
SourceBufferList::Length()
{
MOZ_ASSERT(NS_IsMainThread());
return mSourceBuffers.Length();
}
void
SourceBufferList::Append(SourceBuffer* aSourceBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
mSourceBuffers.AppendElement(aSourceBuffer);
QueueAsyncSimpleEvent("addsourcebuffer");
}
void
SourceBufferList::AppendSimple(SourceBuffer* aSourceBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
mSourceBuffers.AppendElement(aSourceBuffer);
}
void
SourceBufferList::Remove(SourceBuffer* aSourceBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ALWAYS_TRUE(mSourceBuffers.RemoveElement(aSourceBuffer));
aSourceBuffer->Detach();
QueueAsyncSimpleEvent("removesourcebuffer");
}
bool
SourceBufferList::Contains(SourceBuffer* aSourceBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
return mSourceBuffers.Contains(aSourceBuffer);
}
void
SourceBufferList::Clear()
{
MOZ_ASSERT(NS_IsMainThread());
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
mSourceBuffers[i]->Detach();
}
mSourceBuffers.Clear();
QueueAsyncSimpleEvent("removesourcebuffer");
}
void
SourceBufferList::ClearSimple()
{
MOZ_ASSERT(NS_IsMainThread());
mSourceBuffers.Clear();
}
bool
SourceBufferList::IsEmpty()
{
MOZ_ASSERT(NS_IsMainThread());
return mSourceBuffers.IsEmpty();
}
bool
SourceBufferList::AnyUpdating()
{
MOZ_ASSERT(NS_IsMainThread());
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
if (mSourceBuffers[i]->Updating()) {
return true;
}
}
return false;
}
void
SourceBufferList::RangeRemoval(double aStart, double aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("RangeRemoval(aStart=%f, aEnd=%f)", aStart, aEnd);
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
mSourceBuffers[i]->RangeRemoval(aStart, aEnd);
}
}
void
SourceBufferList::Ended()
{
MOZ_ASSERT(NS_IsMainThread());
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
mSourceBuffers[i]->Ended();
}
}
double
SourceBufferList::GetHighestBufferedEndTime()
{
MOZ_ASSERT(NS_IsMainThread());
double highestEndTime = 0;
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
highestEndTime = std::max(highestEndTime, mSourceBuffers[i]->GetBufferedEnd());
}
return highestEndTime;
}
void
SourceBufferList::DispatchSimpleEvent(const char* aName)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_API("Dispatch event '%s'", aName);
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
}
void
SourceBufferList::QueueAsyncSimpleEvent(const char* aName)
{
MSE_DEBUG("Queue event '%s'", aName);
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBufferList>(this, aName);
NS_DispatchToMainThread(event);
}
SourceBufferList::SourceBufferList(MediaSource* aMediaSource)
: DOMEventTargetHelper(aMediaSource->GetParentObject())
, mMediaSource(aMediaSource)
{
MOZ_ASSERT(aMediaSource);
}
MediaSource*
SourceBufferList::GetParentObject() const
{
return mMediaSource;
}
double
SourceBufferList::HighestStartTime()
{
MOZ_ASSERT(NS_IsMainThread());
double highestStartTime = 0;
for (auto& sourceBuffer : mSourceBuffers) {
highestStartTime =
std::max(sourceBuffer->HighestStartTime(), highestStartTime);
}
return highestStartTime;
}
double
SourceBufferList::HighestEndTime()
{
MOZ_ASSERT(NS_IsMainThread());
double highestEndTime = 0;
for (auto& sourceBuffer : mSourceBuffers) {
highestEndTime =
std::max(sourceBuffer->HighestEndTime(), highestEndTime);
}
return highestEndTime;
}
JSObject*
SourceBufferList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return SourceBufferListBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBufferList, DOMEventTargetHelper,
mMediaSource, mSourceBuffers)
NS_IMPL_ADDREF_INHERITED(SourceBufferList, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(SourceBufferList, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBufferList)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
#undef MSE_API
#undef MSE_DEBUG
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,106 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_SourceBufferList_h_
#define mozilla_dom_SourceBufferList_h_
#include "SourceBuffer.h"
#include "js/RootingAPI.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
#include "nsTArray.h"
struct JSContext;
class JSObject;
namespace mozilla {
template <typename T> class AsyncEventRunner;
namespace dom {
class MediaSource;
class SourceBufferList final : public DOMEventTargetHelper
{
public:
/** WebIDL Methods. */
SourceBuffer* IndexedGetter(uint32_t aIndex, bool& aFound);
uint32_t Length();
IMPL_EVENT_HANDLER(addsourcebuffer);
IMPL_EVENT_HANDLER(removesourcebuffer);
/** End WebIDL methods. */
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SourceBufferList,
DOMEventTargetHelper)
explicit SourceBufferList(MediaSource* aMediaSource);
MediaSource* GetParentObject() const;
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
// Append a SourceBuffer and fire "addsourcebuffer" at the list.
void Append(SourceBuffer* aSourceBuffer);
// Remove a SourceBuffer and fire "removesourcebuffer" at the list.
void Remove(SourceBuffer* aSourceBuffer);
// Returns true if aSourceBuffer is present in the list.
bool Contains(SourceBuffer* aSourceBuffer);
// Remove all SourceBuffers and fire a single "removesourcebuffer" at the list.
void Clear();
// True if list has zero entries.
bool IsEmpty();
// Returns true if updating is true on any SourceBuffers in the list.
bool AnyUpdating();
// Runs the range removal steps from the MSE specification on each SourceBuffer.
void RangeRemoval(double aStart, double aEnd);
// Mark all SourceBuffers input buffers as ended.
void Ended();
// Returns the highest end time of any of the Sourcebuffers.
double GetHighestBufferedEndTime();
// Append a SourceBuffer to the list. No event is fired.
void AppendSimple(SourceBuffer* aSourceBuffer);
// Remove all SourceBuffers from mSourceBuffers.
// No event is fired and no action is performed on the sourcebuffers.
void ClearSimple();
double HighestStartTime();
double HighestEndTime();
private:
~SourceBufferList();
friend class AsyncEventRunner<SourceBufferList>;
void DispatchSimpleEvent(const char* aName);
void QueueAsyncSimpleEvent(const char* aName);
RefPtr<MediaSource> mMediaSource;
nsTArray<RefPtr<SourceBuffer> > mSourceBuffers;
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_SourceBufferList_h_ */

View file

@ -0,0 +1,185 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "SourceBufferResource.h"
#include <algorithm>
#include "nsISeekableStream.h"
#include "nsISupports.h"
#include "mozilla/Logging.h"
#include "MediaData.h"
mozilla::LogModule* GetSourceBufferResourceLog()
{
static mozilla::LazyLogModule sLogModule("SourceBufferResource");
return sLogModule;
}
#define SBR_DEBUG(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define SBR_DEBUGV(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
namespace mozilla {
nsresult
SourceBufferResource::Close()
{
ReentrantMonitorAutoEnter mon(mMonitor);
SBR_DEBUG("Close");
//MOZ_ASSERT(!mClosed);
mClosed = true;
mon.NotifyAll();
return NS_OK;
}
nsresult
SourceBufferResource::ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes)
{
SBR_DEBUG("ReadAt(aOffset=%lld, aBuffer=%p, aCount=%u, aBytes=%p)",
aOffset, aBytes, aCount, aBytes);
ReentrantMonitorAutoEnter mon(mMonitor);
return ReadAtInternal(aOffset, aBuffer, aCount, aBytes, /* aMayBlock = */ true);
}
nsresult
SourceBufferResource::ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes,
bool aMayBlock)
{
mMonitor.AssertCurrentThreadIn();
MOZ_ASSERT_IF(!aMayBlock, aBytes);
if (mClosed ||
aOffset < 0 ||
uint64_t(aOffset) < mInputBuffer.GetOffset() ||
aOffset > GetLength()) {
return NS_ERROR_FAILURE;
}
while (aMayBlock &&
!mEnded &&
aOffset + aCount > GetLength()) {
SBR_DEBUGV("waiting for data");
mMonitor.Wait();
// The callers of this function should have checked this, but it's
// possible that we had an eviction while waiting on the monitor.
if (uint64_t(aOffset) < mInputBuffer.GetOffset()) {
return NS_ERROR_FAILURE;
}
}
uint32_t available = GetLength() - aOffset;
uint32_t count = std::min(aCount, available);
// Keep the position of the last read to have Tell() approximately give us
// the position we're up to in the stream.
mOffset = aOffset + count;
SBR_DEBUGV("offset=%llu GetLength()=%u available=%u count=%u mEnded=%d",
aOffset, GetLength(), available, count, mEnded);
if (available == 0) {
SBR_DEBUGV("reached EOF");
*aBytes = 0;
return NS_OK;
}
mInputBuffer.CopyData(aOffset, count, aBuffer);
*aBytes = count;
return NS_OK;
}
nsresult
SourceBufferResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
{
SBR_DEBUG("ReadFromCache(aBuffer=%p, aOffset=%lld, aCount=%u)",
aBuffer, aOffset, aCount);
ReentrantMonitorAutoEnter mon(mMonitor);
uint32_t bytesRead;
nsresult rv = ReadAtInternal(aOffset, aBuffer, aCount, &bytesRead, /* aMayBlock = */ false);
NS_ENSURE_SUCCESS(rv, rv);
// ReadFromCache return failure if not all the data is cached.
return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
}
uint32_t
SourceBufferResource::EvictData(uint64_t aPlaybackOffset, int64_t aThreshold,
ErrorResult& aRv)
{
SBR_DEBUG("EvictData(aPlaybackOffset=%llu,"
"aThreshold=%u)", aPlaybackOffset, aThreshold);
ReentrantMonitorAutoEnter mon(mMonitor);
uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold, aRv);
if (result > 0) {
// Wake up any waiting threads in case a ReadInternal call
// is now invalid.
mon.NotifyAll();
}
return result;
}
void
SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
{
SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset);
ReentrantMonitorAutoEnter mon(mMonitor);
mInputBuffer.EvictBefore(aOffset, aRv);
// Wake up any waiting threads in case a ReadInternal call
// is now invalid.
mon.NotifyAll();
}
uint32_t
SourceBufferResource::EvictAll()
{
SBR_DEBUG("EvictAll()");
ReentrantMonitorAutoEnter mon(mMonitor);
return mInputBuffer.EvictAll();
}
void
SourceBufferResource::AppendData(MediaByteBuffer* aData)
{
SBR_DEBUG("AppendData(aData=%p, aLength=%u)",
aData->Elements(), aData->Length());
ReentrantMonitorAutoEnter mon(mMonitor);
mInputBuffer.AppendItem(aData);
mEnded = false;
mon.NotifyAll();
}
void
SourceBufferResource::Ended()
{
SBR_DEBUG("");
ReentrantMonitorAutoEnter mon(mMonitor);
mEnded = true;
mon.NotifyAll();
}
SourceBufferResource::~SourceBufferResource()
{
SBR_DEBUG("");
MOZ_COUNT_DTOR(SourceBufferResource);
}
SourceBufferResource::SourceBufferResource(const nsACString& aType)
: mType(aType)
, mMonitor("mozilla::SourceBufferResource::mMonitor")
, mOffset(0)
, mClosed(false)
, mEnded(false)
{
SBR_DEBUG("");
MOZ_COUNT_CTOR(SourceBufferResource);
}
#undef SBR_DEBUG
#undef SBR_DEBUGV
} // namespace mozilla

View file

@ -0,0 +1,161 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_SOURCEBUFFERRESOURCE_H_
#define MOZILLA_SOURCEBUFFERRESOURCE_H_
#include "MediaCache.h"
#include "MediaResource.h"
#include "ResourceQueue.h"
#include "mozilla/Attributes.h"
#include "mozilla/ReentrantMonitor.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nscore.h"
#include "mozilla/Logging.h"
#define UNIMPLEMENTED() { /* Logging this is too spammy to do by default */ }
class nsIStreamListener;
namespace mozilla {
class MediaDecoder;
class MediaByteBuffer;
namespace dom {
class SourceBuffer;
} // namespace dom
class SourceBufferResource final : public MediaResource
{
public:
explicit SourceBufferResource(const nsACString& aType);
nsresult Close() override;
void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
void Resume() override { UNIMPLEMENTED(); }
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override { UNIMPLEMENTED(); return nullptr; }
already_AddRefed<MediaResource> CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; }
void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
int64_t Tell() override { return mOffset; }
void Pin() override { UNIMPLEMENTED(); }
void Unpin() override { UNIMPLEMENTED(); }
double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
int64_t GetLength() override { return mInputBuffer.GetLength(); }
int64_t GetNextCachedData(int64_t aOffset) override {
ReentrantMonitorAutoEnter mon(mMonitor);
MOZ_ASSERT(aOffset >= 0);
if (uint64_t(aOffset) < mInputBuffer.GetOffset()) {
return mInputBuffer.GetOffset();
} else if (aOffset == GetLength()) {
return -1;
}
return aOffset;
}
int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; }
bool IsDataCachedToEndOfResource(int64_t aOffset) override { return false; }
bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; }
bool IsSuspended() override { UNIMPLEMENTED(); return false; }
nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
bool IsTransportSeekable() override { UNIMPLEMENTED(); return true; }
nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override
{
ReentrantMonitorAutoEnter mon(mMonitor);
if (mInputBuffer.GetLength()) {
aRanges += MediaByteRange(mInputBuffer.GetOffset(),
mInputBuffer.GetLength());
}
return NS_OK;
}
const nsCString& GetContentType() const override { return mType; }
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
{
ReentrantMonitorAutoEnter mon(mMonitor);
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
size += mType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
size += mInputBuffer.SizeOfExcludingThis(aMallocSizeOf);
return size;
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
bool IsExpectingMoreData() override
{
return false;
}
// Used by SourceBuffer.
void AppendData(MediaByteBuffer* aData);
void Ended();
bool IsEnded()
{
ReentrantMonitorAutoEnter mon(mMonitor);
return mEnded;
}
// Remove data from resource if it holds more than the threshold reduced by
// the given number of bytes. Returns amount evicted.
uint32_t EvictData(uint64_t aPlaybackOffset, int64_t aThresholdReduct,
ErrorResult& aRv);
// Remove data from resource before the given offset.
void EvictBefore(uint64_t aOffset, ErrorResult& aRv);
// Remove all data from the resource
uint32_t EvictAll();
// Returns the amount of data currently retained by this resource.
int64_t GetSize() {
ReentrantMonitorAutoEnter mon(mMonitor);
return mInputBuffer.GetLength() - mInputBuffer.GetOffset();
}
#if defined(DEBUG)
void Dump(const char* aPath) {
mInputBuffer.Dump(aPath);
}
#endif
private:
virtual ~SourceBufferResource();
nsresult ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock);
const nsCString mType;
// Provides synchronization between SourceBuffers and InputAdapters.
// Protects all of the member variables below. Read() will await a
// Notify() (from Seek, AppendData, Ended, or Close) when insufficient
// data is available in mData.
mutable ReentrantMonitor mMonitor;
// The buffer holding resource data.
ResourceQueue mInputBuffer;
uint64_t mOffset;
bool mClosed;
bool mEnded;
};
} // namespace mozilla
#undef UNIMPLEMENTED
#endif /* MOZILLA_SOURCEBUFFERRESOURCE_H_ */

View file

@ -0,0 +1,111 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_SOURCEBUFFERTASK_H_
#define MOZILLA_SOURCEBUFFERTASK_H_
#include "mozilla/MozPromise.h"
#include "mozilla/Pair.h"
#include "mozilla/RefPtr.h"
#include "SourceBufferAttributes.h"
#include "TimeUnits.h"
#include "MediaResult.h"
namespace mozilla {
class SourceBufferTask {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferTask);
enum class Type {
AppendBuffer,
Abort,
Reset,
RangeRemoval,
EvictData,
Detach
};
typedef Pair<bool, SourceBufferAttributes> AppendBufferResult;
typedef MozPromise<AppendBufferResult, MediaResult, /* IsExclusive = */ true> AppendPromise;
typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> RangeRemovalPromise;
virtual Type GetType() const = 0;
template<typename ReturnType>
ReturnType* As()
{
MOZ_ASSERT(this->GetType() == ReturnType::sType);
return static_cast<ReturnType*>(this);
}
protected:
virtual ~SourceBufferTask() {}
};
class AppendBufferTask : public SourceBufferTask {
public:
AppendBufferTask(MediaByteBuffer* aData,
SourceBufferAttributes aAttributes)
: mBuffer(aData)
, mAttributes(aAttributes)
{}
static const Type sType = Type::AppendBuffer;
Type GetType() const override { return Type::AppendBuffer; }
RefPtr<MediaByteBuffer> mBuffer;
SourceBufferAttributes mAttributes;
MozPromiseHolder<AppendPromise> mPromise;
};
class AbortTask : public SourceBufferTask {
public:
static const Type sType = Type::Abort;
Type GetType() const override { return Type::Abort; }
};
class ResetTask : public SourceBufferTask {
public:
static const Type sType = Type::Reset;
Type GetType() const override { return Type::Reset; }
};
class RangeRemovalTask : public SourceBufferTask {
public:
explicit RangeRemovalTask(const media::TimeInterval& aRange)
: mRange(aRange)
{}
static const Type sType = Type::RangeRemoval;
Type GetType() const override { return Type::RangeRemoval; }
media::TimeInterval mRange;
MozPromiseHolder<RangeRemovalPromise> mPromise;
};
class EvictDataTask : public SourceBufferTask {
public:
EvictDataTask(const media::TimeUnit& aPlaybackTime, int64_t aSizetoEvict)
: mPlaybackTime(aPlaybackTime)
, mSizeToEvict(aSizetoEvict)
{}
static const Type sType = Type::EvictData;
Type GetType() const override { return Type::EvictData; }
media::TimeUnit mPlaybackTime;
int64_t mSizeToEvict;
};
class DetachTask : public SourceBufferTask {
public:
static const Type sType = Type::Detach;
Type GetType() const override { return Type::Detach; }
};
} // end mozilla namespace
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,500 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_TRACKBUFFERSMANAGER_H_
#define MOZILLA_TRACKBUFFERSMANAGER_H_
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "AutoTaskQueue.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "MediaData.h"
#include "MediaDataDemuxer.h"
#include "MediaResult.h"
#include "MediaSourceDecoder.h"
#include "SourceBufferTask.h"
#include "TimeUnits.h"
#include "nsAutoPtr.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsTArray.h"
namespace mozilla {
class ContainerParser;
class MediaByteBuffer;
class MediaRawData;
class MediaSourceDemuxer;
class SourceBufferResource;
class SourceBufferTaskQueue
{
public:
SourceBufferTaskQueue()
: mMonitor("SourceBufferTaskQueue")
{}
~SourceBufferTaskQueue()
{
MOZ_ASSERT(mQueue.IsEmpty(), "All tasks must have been processed");
}
void Push(SourceBufferTask* aTask)
{
MonitorAutoLock mon(mMonitor);
mQueue.AppendElement(aTask);
}
already_AddRefed<SourceBufferTask> Pop()
{
MonitorAutoLock mon(mMonitor);
if (!mQueue.Length()) {
return nullptr;
}
RefPtr<SourceBufferTask> task = Move(mQueue[0]);
mQueue.RemoveElementAt(0);
return task.forget();
}
nsTArray<SourceBufferTask>::size_type Length() const
{
MonitorAutoLock mon(mMonitor);
return mQueue.Length();
}
private:
mutable Monitor mMonitor;
nsTArray<RefPtr<SourceBufferTask>> mQueue;
};
class TrackBuffersManager
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffersManager);
enum class EvictDataResult : int8_t
{
NO_DATA_EVICTED,
CANT_EVICT,
BUFFER_FULL,
};
typedef TrackInfo::TrackType TrackType;
typedef MediaData::Type MediaType;
typedef nsTArray<RefPtr<MediaRawData>> TrackBuffer;
typedef SourceBufferTask::AppendPromise AppendPromise;
typedef SourceBufferTask::RangeRemovalPromise RangeRemovalPromise;
// Interface for SourceBuffer
TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
const nsACString& aType);
// Queue a task to add data to the end of the input buffer and run the MSE
// Buffer Append Algorithm
// 3.5.5 Buffer Append Algorithm.
// http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
const SourceBufferAttributes& aAttributes);
// Queue a task to abort any pending AppendData.
// Does nothing at this stage.
void AbortAppendData();
// Queue a task to run MSE Reset Parser State Algorithm.
// 3.5.2 Reset Parser State
void ResetParserState(SourceBufferAttributes& aAttributes);
// Queue a task to run the MSE range removal algorithm.
// http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
media::TimeUnit aEnd);
// Schedule data eviction if necessary as the next call to AppendData will
// add aSize bytes.
// Eviction is done in two steps, first remove data up to aPlaybackTime
// and if still more space is needed remove from the end.
EvictDataResult EvictData(const media::TimeUnit& aPlaybackTime, int64_t aSize);
// Returns the buffered range currently managed.
// This may be called on any thread.
// Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
media::TimeIntervals Buffered() const;
media::TimeUnit HighestStartTime() const;
media::TimeUnit HighestEndTime() const;
// Return the size of the data managed by this SourceBufferContentManager.
int64_t GetSize() const;
// Indicate that the MediaSource parent object got into "ended" state.
void Ended();
// The parent SourceBuffer is about to be destroyed.
void Detach();
int64_t EvictionThreshold() const;
// Interface for MediaSourceDemuxer
MediaInfo GetMetadata() const;
const TrackBuffer& GetTrackBuffer(TrackInfo::TrackType aTrack) const;
const media::TimeIntervals& Buffered(TrackInfo::TrackType) const;
const media::TimeUnit& HighestStartTime(TrackInfo::TrackType) const;
media::TimeIntervals SafeBuffered(TrackInfo::TrackType) const;
bool IsEnded() const
{
return mEnded;
}
uint32_t Evictable(TrackInfo::TrackType aTrack) const;
media::TimeUnit Seek(TrackInfo::TrackType aTrack,
const media::TimeUnit& aTime,
const media::TimeUnit& aFuzz);
uint32_t SkipToNextRandomAccessPoint(TrackInfo::TrackType aTrack,
const media::TimeUnit& aTimeThreadshold,
const media::TimeUnit& aFuzz,
bool& aFound);
already_AddRefed<MediaRawData> GetSample(TrackInfo::TrackType aTrack,
const media::TimeUnit& aFuzz,
MediaResult& aResult);
int32_t FindCurrentPosition(TrackInfo::TrackType aTrack,
const media::TimeUnit& aFuzz) const;
media::TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack,
const media::TimeUnit& aFuzz);
void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes) const;
private:
typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
// for MediaSourceDemuxer::GetMozDebugReaderData
friend class MediaSourceDemuxer;
~TrackBuffersManager();
// All following functions run on the taskqueue.
RefPtr<AppendPromise> DoAppendData(RefPtr<MediaByteBuffer> aData,
SourceBufferAttributes aAttributes);
void ScheduleSegmentParserLoop();
void SegmentParserLoop();
void InitializationSegmentReceived();
void ShutdownDemuxers();
void CreateDemuxerforMIMEType();
void ResetDemuxingState();
void NeedMoreData();
void RejectAppend(const MediaResult& aRejectValue, const char* aName);
// Will return a promise that will be resolved once all frames of the current
// media segment have been processed.
RefPtr<CodedFrameProcessingPromise> CodedFrameProcessing();
void CompleteCodedFrameProcessing();
// Called by ResetParserState.
void CompleteResetParserState();
RefPtr<RangeRemovalPromise>
CodedFrameRemovalWithPromise(media::TimeInterval aInterval);
bool CodedFrameRemoval(media::TimeInterval aInterval);
void SetAppendState(SourceBufferAttributes::AppendState aAppendState);
bool HasVideo() const
{
return mVideoTracks.mNumTracks > 0;
}
bool HasAudio() const
{
return mAudioTracks.mNumTracks > 0;
}
// The input buffer as per http://w3c.github.io/media-source/index.html#sourcebuffer-input-buffer
RefPtr<MediaByteBuffer> mInputBuffer;
// Buffer full flag as per https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag.
// Accessed on both the main thread and the task queue.
Atomic<bool> mBufferFull;
bool mFirstInitializationSegmentReceived;
// Set to true once a new segment is started.
bool mNewMediaSegmentStarted;
bool mActiveTrack;
nsCString mType;
// ContainerParser objects and methods.
// Those are used to parse the incoming input buffer.
// Recreate the ContainerParser and if aReuseInitData is true then
// feed it with the previous init segment found.
void RecreateParser(bool aReuseInitData);
nsAutoPtr<ContainerParser> mParser;
// Demuxer objects and methods.
void AppendDataToCurrentInputBuffer(MediaByteBuffer* aData);
RefPtr<MediaByteBuffer> mInitData;
// Temporary input buffer to handle partial media segment header.
// We store the current input buffer content into it should we need to
// reinitialize the demuxer once we have some samples and a discontinuity is
// detected.
RefPtr<MediaByteBuffer> mPendingInputBuffer;
RefPtr<SourceBufferResource> mCurrentInputBuffer;
RefPtr<MediaDataDemuxer> mInputDemuxer;
// Length already processed in current media segment.
uint64_t mProcessedInput;
Maybe<media::TimeUnit> mLastParsedEndTime;
void OnDemuxerInitDone(nsresult);
void OnDemuxerInitFailed(const MediaResult& aFailure);
void OnDemuxerResetDone(nsresult);
MozPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest;
void OnDemuxFailed(TrackType aTrack, const MediaResult& aError);
void DoDemuxVideo();
void OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
void OnVideoDemuxFailed(const MediaResult& aError)
{
mVideoTracks.mDemuxRequest.Complete();
OnDemuxFailed(TrackType::kVideoTrack, aError);
}
void DoDemuxAudio();
void OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
void OnAudioDemuxFailed(const MediaResult& aError)
{
mAudioTracks.mDemuxRequest.Complete();
OnDemuxFailed(TrackType::kAudioTrack, aError);
}
void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aSizeToEvict);
struct TrackData
{
TrackData()
: mNumTracks(0)
, mNeedRandomAccessPoint(true)
, mSizeBuffer(0)
{}
uint32_t mNumTracks;
// Definition of variables:
// https://w3c.github.io/media-source/#track-buffers
// Last decode timestamp variable that stores the decode timestamp of the
// last coded frame appended in the current coded frame group.
// The variable is initially unset to indicate that no coded frames have
// been appended yet.
Maybe<media::TimeUnit> mLastDecodeTimestamp;
// Last frame duration variable that stores the coded frame duration of the
// last coded frame appended in the current coded frame group.
// The variable is initially unset to indicate that no coded frames have
// been appended yet.
Maybe<media::TimeUnit> mLastFrameDuration;
// Highest end timestamp variable that stores the highest coded frame end
// timestamp across all coded frames in the current coded frame group that
// were appended to this track buffer.
// The variable is initially unset to indicate that no coded frames have
// been appended yet.
Maybe<media::TimeUnit> mHighestEndTimestamp;
// Highest presentation timestamp in track buffer.
// Protected by global monitor, except when reading on the task queue as it
// is only written there.
media::TimeUnit mHighestStartTimestamp;
// Longest frame duration seen since last random access point.
// Only ever accessed when mLastDecodeTimestamp and mLastFrameDuration are
// set.
media::TimeUnit mLongestFrameDuration;
// Need random access point flag variable that keeps track of whether the
// track buffer is waiting for a random access point coded frame.
// The variable is initially set to true to indicate that random access
// point coded frame is needed before anything can be added to the track
// buffer.
bool mNeedRandomAccessPoint;
RefPtr<MediaTrackDemuxer> mDemuxer;
MozPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
// Highest end timestamp of the last media segment demuxed.
media::TimeUnit mLastParsedEndTime;
// If set, position where the next contiguous frame will be inserted.
// If a discontinuity is detected, it will be unset and recalculated upon
// the next insertion.
Maybe<uint32_t> mNextInsertionIndex;
// Samples just demuxed, but not yet parsed.
TrackBuffer mQueuedSamples;
const TrackBuffer& GetTrackBuffer() const
{
MOZ_RELEASE_ASSERT(mBuffers.Length(),
"TrackBuffer must have been created");
return mBuffers.LastElement();
}
TrackBuffer& GetTrackBuffer()
{
MOZ_RELEASE_ASSERT(mBuffers.Length(),
"TrackBuffer must have been created");
return mBuffers.LastElement();
}
// We only manage a single track of each type at this time.
nsTArray<TrackBuffer> mBuffers;
// Track buffer ranges variable that represents the presentation time ranges
// occupied by the coded frames currently stored in the track buffer.
media::TimeIntervals mBufferedRanges;
// Sanitized mBufferedRanges with a fuzz of half a sample's duration applied
// This buffered ranges is the basis of what is exposed to the JS.
media::TimeIntervals mSanitizedBufferedRanges;
// Byte size of all samples contained in this track buffer.
uint32_t mSizeBuffer;
// TrackInfo of the first metadata received.
RefPtr<SharedTrackInfo> mInfo;
// TrackInfo of the last metadata parsed (updated with each init segment.
RefPtr<SharedTrackInfo> mLastInfo;
// If set, position of the next sample to be retrieved by GetSample().
// If the position is equal to the TrackBuffer's length, it indicates that
// we've reached EOS.
Maybe<uint32_t> mNextGetSampleIndex;
// Approximation of the next sample's decode timestamp.
media::TimeUnit mNextSampleTimecode;
// Approximation of the next sample's presentation timestamp.
media::TimeUnit mNextSampleTime;
struct EvictionIndex
{
EvictionIndex() { Reset(); }
void Reset()
{
mEvictable = 0;
mLastIndex = 0;
}
uint32_t mEvictable;
uint32_t mLastIndex;
};
// Size of data that can be safely evicted during the next eviction
// cycle.
// We consider as evictable all frames up to the last keyframe prior to
// mNextGetSampleIndex. If mNextGetSampleIndex isn't set, then we assume
// that we can't yet evict data.
// Protected by global monitor, except when reading on the task queue as it
// is only written there.
EvictionIndex mEvictionIndex;
void ResetAppendState()
{
mLastDecodeTimestamp.reset();
mLastFrameDuration.reset();
mHighestEndTimestamp.reset();
mNeedRandomAccessPoint = true;
mNextInsertionIndex.reset();
}
void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes) const;
};
void CheckSequenceDiscontinuity(const media::TimeUnit& aPresentationTime);
void ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData);
media::TimeInterval PresentationInterval(const TrackBuffer& aSamples) const;
bool CheckNextInsertionIndex(TrackData& aTrackData,
const media::TimeUnit& aSampleTime);
void InsertFrames(TrackBuffer& aSamples,
const media::TimeIntervals& aIntervals,
TrackData& aTrackData);
void UpdateHighestTimestamp(TrackData& aTrackData,
const media::TimeUnit& aHighestTime);
// Remove all frames and their dependencies contained in aIntervals.
// Return the index at which frames were first removed or 0 if no frames
// removed.
uint32_t RemoveFrames(const media::TimeIntervals& aIntervals,
TrackData& aTrackData,
uint32_t aStartIndex);
// Recalculate track's evictable amount.
void ResetEvictionIndex(TrackData& aTrackData);
void UpdateEvictionIndex(TrackData& aTrackData, uint32_t aCurrentIndex);
// Find index of sample. Return a negative value if not found.
uint32_t FindSampleIndex(const TrackBuffer& aTrackBuffer,
const media::TimeInterval& aInterval);
const MediaRawData* GetSample(TrackInfo::TrackType aTrack,
uint32_t aIndex,
const media::TimeUnit& aExpectedDts,
const media::TimeUnit& aExpectedPts,
const media::TimeUnit& aFuzz);
void UpdateBufferedRanges();
void RejectProcessing(const MediaResult& aRejectValue, const char* aName);
void ResolveProcessing(bool aResolveValue, const char* aName);
MozPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest;
MozPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise;
// Trackbuffers definition.
nsTArray<const TrackData*> GetTracksList() const;
nsTArray<TrackData*> GetTracksList();
TrackData& GetTracksData(TrackType aTrack)
{
switch(aTrack) {
case TrackType::kVideoTrack:
return mVideoTracks;
case TrackType::kAudioTrack:
default:
return mAudioTracks;
}
}
const TrackData& GetTracksData(TrackType aTrack) const
{
switch(aTrack) {
case TrackType::kVideoTrack:
return mVideoTracks;
case TrackType::kAudioTrack:
default:
return mAudioTracks;
}
}
TrackData mVideoTracks;
TrackData mAudioTracks;
// TaskQueue methods and objects.
AbstractThread* GetTaskQueue() const
{
return mTaskQueue;
}
bool OnTaskQueue() const
{
return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
}
RefPtr<AutoTaskQueue> mTaskQueue;
// SourceBuffer Queues and running context.
SourceBufferTaskQueue mQueue;
void QueueTask(SourceBufferTask* aTask);
void ProcessTasks();
// Set if the TrackBuffersManager is currently processing a task.
// At this stage, this task is always a AppendBufferTask.
RefPtr<SourceBufferTask> mCurrentTask;
// Current SourceBuffer state for ongoing task.
// Its content is returned to the SourceBuffer once the AppendBufferTask has
// completed.
UniquePtr<SourceBufferAttributes> mSourceBufferAttributes;
// The current sourcebuffer append window. It's content is equivalent to
// mSourceBufferAttributes.mAppendWindowStart/End
media::TimeInterval mAppendWindow;
// Strong references to external objects.
nsMainThreadPtrHandle<MediaSourceDecoder> mParentDecoder;
// Return public highest end time across all aTracks.
// Monitor must be held.
media::TimeUnit HighestEndTime(nsTArray<const media::TimeIntervals*>& aTracks) const;
// Set to true if mediasource state changed to ended.
Atomic<bool> mEnded;
// Global size of this source buffer content.
Atomic<int64_t> mSizeSourceBuffer;
const int64_t mVideoEvictionThreshold;
const int64_t mAudioEvictionThreshold;
enum class EvictionState
{
NO_EVICTION_NEEDED,
EVICTION_NEEDED,
EVICTION_COMPLETED,
};
Atomic<EvictionState> mEvictionState;
// Monitor to protect following objects accessed across multiple threads.
mutable Monitor mMonitor;
// Stable audio and video track time ranges.
media::TimeIntervals mVideoBufferedRanges;
media::TimeIntervals mAudioBufferedRanges;
// MediaInfo of the first init segment read.
MediaInfo mInfo;
};
} // namespace mozilla
#endif /* MOZILLA_TRACKBUFFERSMANAGER_H_ */

View file

@ -0,0 +1,92 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 <gtest/gtest.h>
#include <stdint.h>
#include "ContainerParser.h"
#include "mozilla/ArrayUtils.h"
#include "nsAutoPtr.h"
using namespace mozilla;
TEST(ContainerParser, MIMETypes) {
const char* content_types[] = {
"video/webm",
"audio/webm",
"video/mp4",
"audio/mp4",
"audio/aac"
};
nsAutoPtr<ContainerParser> parser;
for (size_t i = 0; i < ArrayLength(content_types); ++i) {
nsAutoCString content_type(content_types[i]);
parser = ContainerParser::CreateForMIMEType(content_type);
ASSERT_NE(parser, nullptr);
}
}
already_AddRefed<MediaByteBuffer> make_adts_header()
{
const uint8_t test[] = { 0xff, 0xf1, 0x50, 0x80, 0x03, 0x1f, 0xfc };
RefPtr<MediaByteBuffer> buffer(new MediaByteBuffer);
buffer->AppendElements(test, ArrayLength(test));
return buffer.forget();
}
TEST(ContainerParser, ADTSHeader) {
nsAutoPtr<ContainerParser> parser;
parser = ContainerParser::CreateForMIMEType(NS_LITERAL_CSTRING("audio/aac"));
ASSERT_NE(parser, nullptr);
// Audio data should have no gaps.
EXPECT_EQ(parser->GetRoundingError(), 0);
// Test a valid header.
RefPtr<MediaByteBuffer> header = make_adts_header();
EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
// Test variations.
uint8_t save = header->ElementAt(1);
for (uint8_t i = 1; i < 3; ++i) {
// Set non-zero layer.
header->ReplaceElementAt(1, (header->ElementAt(1) & 0xf9) | (i << 1));
EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
<< "Accepted non-zero layer in header.";
}
header->ReplaceElementAt(1, save);
save = header->ElementAt(2);
header->ReplaceElementAt(2, (header->ElementAt(2) & 0x3b) | (15 << 2));
EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
<< "Accepted explicit frequency in header.";
header->ReplaceElementAt(2, save);
// Test a short header.
header->SetLength(6);
EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
<< "Accepted too-short header.";
EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
<< "Found media segment when there was just a partial header.";
// Test parse results.
header = make_adts_header();
EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
<< "Found media segment when there was just a header.";
int64_t start = 0;
int64_t end = 0;
EXPECT_TRUE(NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
EXPECT_TRUE(parser->HasInitData());
EXPECT_TRUE(parser->HasCompleteInitData());
MediaByteBuffer* init = parser->InitData();
ASSERT_NE(init, nullptr);
EXPECT_EQ(init->Length(), header->Length());
EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header->Length())));
// Media segment range should be empty here.
EXPECT_EQ(parser->MediaHeaderRange(), MediaByteRange());
EXPECT_EQ(parser->MediaSegmentRange(), MediaByteRange());
}

View file

@ -0,0 +1,16 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'TestContainerParser.cpp',
]
LOCAL_INCLUDES += [
'/dom/media',
'/dom/media/mediasource',
]
FINAL_LIBRARY = 'xul-gtest'

View file

@ -0,0 +1,49 @@
# vim: set filetype=python:
# 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/.
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
EXPORTS += [
'AsyncEventRunner.h',
'AutoTaskQueue.h',
'MediaSourceDecoder.h',
'MediaSourceDemuxer.h',
'SourceBufferAttributes.h',
'SourceBufferTask.h',
'TrackBuffersManager.h',
]
EXPORTS.mozilla.dom += [
'MediaSource.h',
'SourceBuffer.h',
'SourceBufferList.h',
]
UNIFIED_SOURCES += [
'ContainerParser.cpp',
'MediaSource.cpp',
'MediaSourceDecoder.cpp',
'MediaSourceDemuxer.cpp',
'MediaSourceUtils.cpp',
'ResourceQueue.cpp',
'SourceBuffer.cpp',
'SourceBufferList.cpp',
'SourceBufferResource.cpp',
'TrackBuffersManager.cpp',
]
TEST_DIRS += [
'gtest',
]
if CONFIG['MOZ_GONK_MEDIACODEC']:
DEFINES['MOZ_GONK_MEDIACODEC'] = True
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

View file

@ -0,0 +1 @@
Cache-Control: no-store

View file

@ -0,0 +1 @@
Cache-Control: no-store

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

Binary file not shown.

View file

@ -0,0 +1 @@
Cache-Control: no-store

View file

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H0M9.98S" profiles="urn:mpeg:dash:profile:full:2011">
<ProgramInformation moreInformationURL="http://gpac.sourceforge.net">
<Title>bipbop_dash.mpd handcrafted by JYA</Title>
</ProgramInformation>
<Period duration="PT0H0M9.98S">
<AdaptationSet segmentAlignment="true" maxWidth="400" maxHeight="300" maxFrameRate="90000" par="4:3" lang="und">
<Representation id="1" mimeType="video/mp4" codecs="avc1.4d4015" width="400" height="300" frameRate="90000" sar="1:1" startWithSAP="1" bandwidth="226425">
<SegmentList timescale="90000" duration="69043">
<Initialization sourceURL="bipbop_videoinit.mp4"/>
<SegmentURL media="bipbop_video1.m4s"/>
<SegmentURL media="bipbop_video2.m4s"/>
<SegmentURL media="bipbop_video3.m4s"/>
<SegmentURL media="bipbop_video4.m4s"/>
<SegmentURL media="bipbop_video5.m4s"/>
<SegmentURL media="bipbop_video6.m4s"/>
<SegmentURL media="bipbop_video7.m4s"/>
<SegmentURL media="bipbop_video8.m4s"/>
<SegmentURL media="bipbop_video9.m4s"/>
<SegmentURL media="bipbop_video10.m4s"/>
<SegmentURL media="bipbop_video11.m4s"/>
<SegmentURL media="bipbop_video12.m4s"/>
<SegmentURL media="bipbop_video13.m4s"/>
</SegmentList>
</Representation>
</AdaptationSet>
<AdaptationSet segmentAlignment="true" lang="und">
<Representation id="1" mimeType="audio/mp4" codecs="mp4a.40.2" audioSamplingRate="22050" startWithSAP="1" bandwidth="7206">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<SegmentList timescale="22050" duration="20101">
<Initialization sourceURL="bipbop_audioinit.mp4"/>
<SegmentURL media="bipbop_audio1.m4s"/>
<SegmentURL media="bipbop_audio2.m4s"/>
<SegmentURL media="bipbop_audio3.m4s"/>
<SegmentURL media="bipbop_audio4.m4s"/>
<SegmentURL media="bipbop_audio5.m4s"/>
<SegmentURL media="bipbop_audio6.m4s"/>
<SegmentURL media="bipbop_audio7.m4s"/>
<SegmentURL media="bipbop_audio8.m4s"/>
<SegmentURL media="bipbop_audio9.m4s"/>
<SegmentURL media="bipbop_audio10.m4s"/>
<SegmentURL media="bipbop_audio11.m4s"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more