mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-28 11:27:32 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
36
dom/media/mediasource/AsyncEventRunner.h
Normal file
36
dom/media/mediasource/AsyncEventRunner.h
Normal 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_ */
|
||||
59
dom/media/mediasource/AutoTaskQueue.h
Normal file
59
dom/media/mediasource/AutoTaskQueue.h
Normal 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
|
||||
716
dom/media/mediasource/ContainerParser.cpp
Normal file
716
dom/media/mediasource/ContainerParser.cpp
Normal 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
|
||||
90
dom/media/mediasource/ContainerParser.h
Normal file
90
dom/media/mediasource/ContainerParser.h
Normal 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_ */
|
||||
591
dom/media/mediasource/MediaSource.cpp
Normal file
591
dom/media/mediasource/MediaSource.cpp
Normal 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
|
||||
157
dom/media/mediasource/MediaSource.h
Normal file
157
dom/media/mediasource/MediaSource.h
Normal 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_ */
|
||||
361
dom/media/mediasource/MediaSourceDecoder.cpp
Normal file
361
dom/media/mediasource/MediaSourceDecoder.cpp
Normal 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
|
||||
96
dom/media/mediasource/MediaSourceDecoder.h
Normal file
96
dom/media/mediasource/MediaSourceDecoder.h
Normal 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_ */
|
||||
499
dom/media/mediasource/MediaSourceDemuxer.cpp
Normal file
499
dom/media/mediasource/MediaSourceDemuxer.cpp
Normal 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
|
||||
145
dom/media/mediasource/MediaSourceDemuxer.h
Normal file
145
dom/media/mediasource/MediaSourceDemuxer.h
Normal 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
|
||||
109
dom/media/mediasource/MediaSourceResource.h
Normal file
109
dom/media/mediasource/MediaSourceResource.h
Normal 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_ */
|
||||
34
dom/media/mediasource/MediaSourceUtils.cpp
Normal file
34
dom/media/mediasource/MediaSourceUtils.cpp
Normal 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
|
||||
19
dom/media/mediasource/MediaSourceUtils.h
Normal file
19
dom/media/mediasource/MediaSourceUtils.h
Normal 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_ */
|
||||
214
dom/media/mediasource/ResourceQueue.cpp
Normal file
214
dom/media/mediasource/ResourceQueue.cpp
Normal 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
|
||||
86
dom/media/mediasource/ResourceQueue.h
Normal file
86
dom/media/mediasource/ResourceQueue.h
Normal 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_ */
|
||||
596
dom/media/mediasource/SourceBuffer.cpp
Normal file
596
dom/media/mediasource/SourceBuffer.cpp
Normal 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
|
||||
189
dom/media/mediasource/SourceBuffer.h
Normal file
189
dom/media/mediasource/SourceBuffer.h
Normal 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_ */
|
||||
157
dom/media/mediasource/SourceBufferAttributes.h
Normal file
157
dom/media/mediasource/SourceBufferAttributes.h
Normal 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_ */
|
||||
225
dom/media/mediasource/SourceBufferList.cpp
Normal file
225
dom/media/mediasource/SourceBufferList.cpp
Normal 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
|
||||
106
dom/media/mediasource/SourceBufferList.h
Normal file
106
dom/media/mediasource/SourceBufferList.h
Normal 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_ */
|
||||
185
dom/media/mediasource/SourceBufferResource.cpp
Normal file
185
dom/media/mediasource/SourceBufferResource.cpp
Normal 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
|
||||
161
dom/media/mediasource/SourceBufferResource.h
Normal file
161
dom/media/mediasource/SourceBufferResource.h
Normal 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_ */
|
||||
111
dom/media/mediasource/SourceBufferTask.h
Normal file
111
dom/media/mediasource/SourceBufferTask.h
Normal 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
|
||||
2505
dom/media/mediasource/TrackBuffersManager.cpp
Normal file
2505
dom/media/mediasource/TrackBuffersManager.cpp
Normal file
File diff suppressed because it is too large
Load diff
500
dom/media/mediasource/TrackBuffersManager.h
Normal file
500
dom/media/mediasource/TrackBuffersManager.h
Normal 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_ */
|
||||
92
dom/media/mediasource/gtest/TestContainerParser.cpp
Normal file
92
dom/media/mediasource/gtest/TestContainerParser.cpp
Normal 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());
|
||||
}
|
||||
16
dom/media/mediasource/gtest/moz.build
Normal file
16
dom/media/mediasource/gtest/moz.build
Normal 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'
|
||||
49
dom/media/mediasource/moz.build
Normal file
49
dom/media/mediasource/moz.build
Normal 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']
|
||||
BIN
dom/media/mediasource/test/aac20-48000-64000-1.m4s
Normal file
BIN
dom/media/mediasource/test/aac20-48000-64000-1.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/aac20-48000-64000-2.m4s
Normal file
BIN
dom/media/mediasource/test/aac20-48000-64000-2.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/aac20-48000-64000-init.mp4
Normal file
BIN
dom/media/mediasource/test/aac20-48000-64000-init.mp4
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/aac51-48000-128000-1.m4s
Normal file
BIN
dom/media/mediasource/test/aac51-48000-128000-1.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/aac51-48000-128000-2.m4s
Normal file
BIN
dom/media/mediasource/test/aac51-48000-128000-2.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/aac51-48000-128000-init.mp4
Normal file
BIN
dom/media/mediasource/test/aac51-48000-128000-init.mp4
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop1.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop1.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop1.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop1.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop10.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop10.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop10.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop10.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop11.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop11.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop11.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop11.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop12.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop12.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop12.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop12.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop13.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop13.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop13.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop13.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop2.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop2.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop2.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop2.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop2s.mp4
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop2s.mp4
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop2s.mp4^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop2s.mp4^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop3.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop3.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop3.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop3.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop4.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop4.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop4.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop4.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop5.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop5.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop5.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop5.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop6.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop6.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop6.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop6.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop7.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop7.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop7.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop7.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop8.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop8.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop8.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop8.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop9.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop9.m4s
Normal file
Binary file not shown.
1
dom/media/mediasource/test/bipbop/bipbop9.m4s^headers^
Normal file
1
dom/media/mediasource/test/bipbop/bipbop9.m4s^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video1.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video1.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video2.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video2.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio1.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio1.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio10.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio10.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio11.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio11.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio2.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio2.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio3.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio3.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio4.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio4.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio5.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio5.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio6.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio6.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio7.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio7.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio8.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio8.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audio9.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audio9.m4s
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_audioinit.mp4
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_audioinit.mp4
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
||||
48
dom/media/mediasource/test/bipbop/bipbop_dash.mpd
Normal file
48
dom/media/mediasource/test/bipbop/bipbop_dash.mpd
Normal 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>
|
||||
BIN
dom/media/mediasource/test/bipbop/bipbop_video1.m4s
Normal file
BIN
dom/media/mediasource/test/bipbop/bipbop_video1.m4s
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue