mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 16:28:38 +09:00
Remove AndroidMediaDecoder and friends
They are no longer supported and don't work with newer Android versions anyway.
This commit is contained in:
parent
5b0a11b442
commit
129388e5e0
15 changed files with 0 additions and 1791 deletions
|
|
@ -18,11 +18,6 @@
|
|||
#include "WebMDecoder.h"
|
||||
#include "WebMDemuxer.h"
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
#include "AndroidMediaDecoder.h"
|
||||
#include "AndroidMediaReader.h"
|
||||
#include "AndroidMediaPluginHost.h"
|
||||
#endif
|
||||
#ifdef MOZ_FMP4
|
||||
#include "MP4Decoder.h"
|
||||
#include "MP4Demuxer.h"
|
||||
|
|
@ -106,21 +101,6 @@ IsHttpLiveStreamingType(const nsACString& aType)
|
|||
return CodecListContains(gHttpLiveStreamingTypes, aType);
|
||||
}
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
static bool
|
||||
IsAndroidMediaType(const nsACString& aType)
|
||||
{
|
||||
if (!MediaDecoder::IsAndroidMediaPluginEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char* supportedTypes[] = {
|
||||
"audio/mpeg", "audio/mp4", "video/mp4", "video/x-m4v", nullptr
|
||||
};
|
||||
return CodecListContains(supportedTypes, aType);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_FMP4
|
||||
static bool
|
||||
IsMP4SupportedType(const MediaContentType& aParsedType,
|
||||
|
|
@ -235,11 +215,6 @@ CanHandleCodecsType(const MediaContentType& aType,
|
|||
if (IsFlacSupportedType(aType.GetMIMEType(), aType.GetCodecs())) {
|
||||
return CANPLAY_YES;
|
||||
}
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled()) {
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType.GetMIMEType(), &codecList);
|
||||
}
|
||||
#endif
|
||||
if (!codecList) {
|
||||
return CANPLAY_MAYBE;
|
||||
}
|
||||
|
|
@ -305,12 +280,6 @@ CanHandleMediaType(const MediaContentType& aType,
|
|||
if (IsFlacSupportedType(aType.GetMIMEType())) {
|
||||
return CANPLAY_MAYBE;
|
||||
}
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType.GetMIMEType(), nullptr)) {
|
||||
return CANPLAY_MAYBE;
|
||||
}
|
||||
#endif
|
||||
return CANPLAY_NO;
|
||||
}
|
||||
|
||||
|
|
@ -391,13 +360,6 @@ InstantiateDecoder(const nsACString& aType,
|
|||
decoder = new FlacDecoder(aOwner);
|
||||
return decoder.forget();
|
||||
}
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType, nullptr)) {
|
||||
decoder = new AndroidMediaDecoder(aOwner, aType);
|
||||
return decoder.forget();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IsWebMSupportedType(aType)) {
|
||||
decoder = new WebMDecoder(aOwner);
|
||||
|
|
@ -446,12 +408,6 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
|
|||
if (IsOggSupportedType(aType)) {
|
||||
decoderReader = new MediaFormatReader(aDecoder, new OggDemuxer(aDecoder->GetResource()));
|
||||
} else
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType, nullptr)) {
|
||||
decoderReader = new AndroidMediaReader(aDecoder, aType);
|
||||
} else
|
||||
#endif
|
||||
if (IsWebMSupportedType(aType)) {
|
||||
decoderReader =
|
||||
new MediaFormatReader(aDecoder, new WebMDemuxer(aDecoder->GetResource()));
|
||||
|
|
@ -474,9 +430,6 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
|||
return
|
||||
IsOggSupportedType(aType) ||
|
||||
IsWebMSupportedType(aType) ||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
(MediaDecoder::IsAndroidMediaPluginEnabled() && IsAndroidMediaType(aType)) ||
|
||||
#endif
|
||||
#ifdef MOZ_FMP4
|
||||
IsMP4SupportedType(aType, /* DecoderDoctorDiagnostics* */ nullptr) ||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -35,10 +35,6 @@
|
|||
#include "Layers.h"
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
#include "AndroidBridge.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::media;
|
||||
|
|
@ -1617,16 +1613,6 @@ MediaDecoder::IsWebMEnabled()
|
|||
return Preferences::GetBool("media.webm.enabled");
|
||||
}
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
bool
|
||||
MediaDecoder::IsAndroidMediaPluginEnabled()
|
||||
{
|
||||
return AndroidBridge::Bridge() &&
|
||||
AndroidBridge::Bridge()->GetAPIVersion() < 16 &&
|
||||
Preferences::GetBool("media.plugins.enabled");
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
|
||||
nsISupports* aData, bool aAnonymize)
|
||||
|
|
|
|||
|
|
@ -447,10 +447,6 @@ private:
|
|||
static bool IsWaveEnabled();
|
||||
static bool IsWebMEnabled();
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
static bool IsAndroidMediaPluginEnabled();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WMF
|
||||
static bool IsWMFEnabled();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
/* -*- 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 "MediaDecoderStateMachine.h"
|
||||
#include "AndroidMediaDecoder.h"
|
||||
#include "AndroidMediaReader.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
AndroidMediaDecoder::AndroidMediaDecoder(MediaDecoderOwner* aOwner,
|
||||
const nsACString& aType)
|
||||
: MediaDecoder(aOwner), mType(aType)
|
||||
{
|
||||
}
|
||||
|
||||
MediaDecoderStateMachine* AndroidMediaDecoder::CreateStateMachine()
|
||||
{
|
||||
return new MediaDecoderStateMachine(this, new AndroidMediaReader(this, mType));
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/* -*- 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(AndroidMediaDecoder_h_)
|
||||
#define AndroidMediaDecoder_h_
|
||||
|
||||
#include "MediaDecoder.h"
|
||||
#include "AndroidMediaDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AndroidMediaDecoder : public MediaDecoder
|
||||
{
|
||||
nsCString mType;
|
||||
public:
|
||||
AndroidMediaDecoder(MediaDecoderOwner* aOwner, const nsACString& aType);
|
||||
|
||||
MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
|
||||
return new AndroidMediaDecoder(aOwner, mType);
|
||||
}
|
||||
MediaDecoderStateMachine* CreateStateMachine() override;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
/* -*- 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 "mozilla/Preferences.h"
|
||||
#include "MediaResource.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "AndroidMediaPluginHost.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsXPCOMStrings.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsIGfxInfo.h"
|
||||
#include "prmem.h"
|
||||
#include "prlink.h"
|
||||
#include "AndroidMediaResourceServer.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
#include "MPAPI.h"
|
||||
|
||||
#include "nsIPropertyBag2.h"
|
||||
|
||||
#if defined(ANDROID)
|
||||
#include "android/log.h"
|
||||
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "AndroidMediaPluginHost" , ## args)
|
||||
#else
|
||||
#define ALOG(args...) /* do nothing */
|
||||
#endif
|
||||
|
||||
using namespace MPAPI;
|
||||
|
||||
Decoder::Decoder() :
|
||||
mResource(nullptr), mPrivate(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static char* GetResource(Decoder *aDecoder)
|
||||
{
|
||||
return static_cast<char*>(aDecoder->mResource);
|
||||
}
|
||||
|
||||
class GetIntPrefEvent : public Runnable {
|
||||
public:
|
||||
GetIntPrefEvent(const char* aPref, int32_t* aResult)
|
||||
: mPref(aPref), mResult(aResult) {}
|
||||
NS_IMETHOD Run() override {
|
||||
return Preferences::GetInt(mPref, mResult);
|
||||
}
|
||||
private:
|
||||
const char* mPref;
|
||||
int32_t* mResult;
|
||||
};
|
||||
|
||||
static bool GetIntPref(const char* aPref, int32_t* aResult)
|
||||
{
|
||||
// GetIntPref() is called on the decoder thread, but the Preferences API
|
||||
// can only be called on the main thread. Post a runnable and wait.
|
||||
NS_ENSURE_TRUE(aPref, false);
|
||||
NS_ENSURE_TRUE(aResult, false);
|
||||
nsCOMPtr<nsIRunnable> event = new GetIntPrefEvent(aPref, aResult);
|
||||
return NS_SUCCEEDED(NS_DispatchToMainThread(event, NS_DISPATCH_SYNC));
|
||||
}
|
||||
|
||||
static bool
|
||||
GetSystemInfoString(const char *aKey, char *aResult, size_t aResultLength)
|
||||
{
|
||||
NS_ENSURE_TRUE(aKey, false);
|
||||
NS_ENSURE_TRUE(aResult, false);
|
||||
|
||||
nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
|
||||
NS_ASSERTION(infoService, "Could not find a system info service");
|
||||
|
||||
nsAutoCString key(aKey);
|
||||
nsAutoCString info;
|
||||
nsresult rv = infoService->GetPropertyAsACString(NS_ConvertUTF8toUTF16(key),
|
||||
info);
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
strncpy(aResult, info.get(), aResultLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static PluginHost sPluginHost = {
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetIntPref,
|
||||
GetSystemInfoString
|
||||
};
|
||||
|
||||
// Return true if Omx decoding is supported on the device. This checks the
|
||||
// built in whitelist/blacklist and preferences to see if that is overridden.
|
||||
static bool IsOmxSupported()
|
||||
{
|
||||
bool forceEnabled =
|
||||
Preferences::GetBool("stagefright.force-enabled", false);
|
||||
bool disabled =
|
||||
Preferences::GetBool("stagefright.disabled", false);
|
||||
|
||||
if (disabled) {
|
||||
NS_WARNING("XXX stagefright disabled\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!forceEnabled) {
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
||||
if (gfxInfo) {
|
||||
int32_t status;
|
||||
nsCString discardFailure;
|
||||
if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_STAGEFRIGHT, discardFailure, &status))) {
|
||||
if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
|
||||
NS_WARNING("XXX stagefright blacklisted\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the name of the shared library that implements Omx based decoding. This varies
|
||||
// depending on libstagefright version installed on the device and whether it is B2G vs Android.
|
||||
// nullptr is returned if Omx decoding is not supported on the device,
|
||||
static const char* GetOmxLibraryName()
|
||||
{
|
||||
#if defined(ANDROID)
|
||||
nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
|
||||
NS_ASSERTION(infoService, "Could not find a system info service");
|
||||
|
||||
int32_t version;
|
||||
nsresult rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"), &version);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ALOG("Android Version is: %d", version);
|
||||
}
|
||||
|
||||
nsAutoString release_version;
|
||||
rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("release_version"), release_version);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ALOG("Android Release Version is: %s", NS_LossyConvertUTF16toASCII(release_version).get());
|
||||
}
|
||||
|
||||
nsAutoString device;
|
||||
rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("device"), device);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ALOG("Android Device is: %s", NS_LossyConvertUTF16toASCII(device).get());
|
||||
}
|
||||
|
||||
nsAutoString manufacturer;
|
||||
rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("manufacturer"), manufacturer);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ALOG("Android Manufacturer is: %s", NS_LossyConvertUTF16toASCII(manufacturer).get());
|
||||
}
|
||||
|
||||
nsAutoString hardware;
|
||||
rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("hardware"), hardware);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ALOG("Android Hardware is: %s", NS_LossyConvertUTF16toASCII(hardware).get());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsOmxSupported())
|
||||
return nullptr;
|
||||
|
||||
#if defined(ANDROID)
|
||||
if (version >= 17) {
|
||||
return "libomxpluginkk.so";
|
||||
}
|
||||
|
||||
// Ice Cream Sandwich and Jellybean
|
||||
return "libomxplugin.so";
|
||||
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
AndroidMediaPluginHost::AndroidMediaPluginHost() {
|
||||
MOZ_COUNT_CTOR(AndroidMediaPluginHost);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mResourceServer = AndroidMediaResourceServer::Start();
|
||||
|
||||
const char* name = GetOmxLibraryName();
|
||||
ALOG("Loading OMX Plugin: %s", name ? name : "nullptr");
|
||||
if (name) {
|
||||
char *path = PR_GetLibraryFilePathname("libxul.so", (PRFuncPtr) GetOmxLibraryName);
|
||||
PRLibrary *lib = nullptr;
|
||||
if (path) {
|
||||
nsAutoCString libpath(path);
|
||||
PR_Free(path);
|
||||
int32_t slash = libpath.RFindChar('/');
|
||||
if (slash != kNotFound) {
|
||||
libpath.Truncate(slash + 1);
|
||||
libpath.Append(name);
|
||||
lib = PR_LoadLibrary(libpath.get());
|
||||
}
|
||||
}
|
||||
if (!lib)
|
||||
lib = PR_LoadLibrary(name);
|
||||
|
||||
if (lib) {
|
||||
Manifest *manifest = static_cast<Manifest *>(PR_FindSymbol(lib, "MPAPI_MANIFEST"));
|
||||
if (manifest) {
|
||||
mPlugins.AppendElement(manifest);
|
||||
ALOG("OMX plugin successfully loaded");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AndroidMediaPluginHost::~AndroidMediaPluginHost() {
|
||||
mResourceServer->Stop();
|
||||
MOZ_COUNT_DTOR(AndroidMediaPluginHost);
|
||||
}
|
||||
|
||||
bool AndroidMediaPluginHost::FindDecoder(const nsACString& aMimeType, const char* const** aCodecs)
|
||||
{
|
||||
const char *chars;
|
||||
size_t len = NS_CStringGetData(aMimeType, &chars, nullptr);
|
||||
for (size_t n = 0; n < mPlugins.Length(); ++n) {
|
||||
Manifest *plugin = mPlugins[n];
|
||||
const char* const *codecs;
|
||||
if (plugin->CanDecode(chars, len, &codecs)) {
|
||||
if (aCodecs)
|
||||
*aCodecs = codecs;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MPAPI::Decoder *AndroidMediaPluginHost::CreateDecoder(MediaResource *aResource, const nsACString& aMimeType)
|
||||
{
|
||||
NS_ENSURE_TRUE(aResource, nullptr);
|
||||
|
||||
nsAutoPtr<Decoder> decoder(new Decoder());
|
||||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *chars;
|
||||
size_t len = NS_CStringGetData(aMimeType, &chars, nullptr);
|
||||
for (size_t n = 0; n < mPlugins.Length(); ++n) {
|
||||
Manifest *plugin = mPlugins[n];
|
||||
const char* const *codecs;
|
||||
if (!plugin->CanDecode(chars, len, &codecs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCString url;
|
||||
nsresult rv = mResourceServer->AddResource(aResource, url);
|
||||
if (NS_FAILED (rv)) continue;
|
||||
|
||||
decoder->mResource = strdup(url.get());
|
||||
if (plugin->CreateDecoder(&sPluginHost, decoder, chars, len)) {
|
||||
return decoder.forget();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AndroidMediaPluginHost::DestroyDecoder(Decoder *aDecoder)
|
||||
{
|
||||
aDecoder->DestroyDecoder(aDecoder);
|
||||
char* resource = GetResource(aDecoder);
|
||||
if (resource) {
|
||||
// resource *shouldn't* be null, but check anyway just in case the plugin
|
||||
// decoder does something stupid.
|
||||
mResourceServer->RemoveResource(nsCString(resource));
|
||||
free(resource);
|
||||
}
|
||||
delete aDecoder;
|
||||
}
|
||||
|
||||
AndroidMediaPluginHost *sAndroidMediaPluginHost = nullptr;
|
||||
AndroidMediaPluginHost *EnsureAndroidMediaPluginHost()
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
||||
if (!sAndroidMediaPluginHost) {
|
||||
sAndroidMediaPluginHost = new AndroidMediaPluginHost();
|
||||
}
|
||||
return sAndroidMediaPluginHost;
|
||||
}
|
||||
|
||||
AndroidMediaPluginHost *GetAndroidMediaPluginHost()
|
||||
{
|
||||
MOZ_ASSERT(sAndroidMediaPluginHost);
|
||||
return sAndroidMediaPluginHost;
|
||||
}
|
||||
|
||||
void AndroidMediaPluginHost::Shutdown()
|
||||
{
|
||||
delete sAndroidMediaPluginHost;
|
||||
sAndroidMediaPluginHost = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/* -*- 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(AndroidMediaPluginHost_h_)
|
||||
#define AndroidMediaPluginHost_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MPAPI.h"
|
||||
#include "AndroidMediaResourceServer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AndroidMediaPluginHost {
|
||||
RefPtr<AndroidMediaResourceServer> mResourceServer;
|
||||
nsTArray<MPAPI::Manifest *> mPlugins;
|
||||
|
||||
MPAPI::Manifest *FindPlugin(const nsACString& aMimeType);
|
||||
public:
|
||||
AndroidMediaPluginHost();
|
||||
~AndroidMediaPluginHost();
|
||||
|
||||
static void Shutdown();
|
||||
|
||||
bool FindDecoder(const nsACString& aMimeType, const char* const** aCodecs);
|
||||
MPAPI::Decoder *CreateDecoder(mozilla::MediaResource *aResource, const nsACString& aMimeType);
|
||||
void DestroyDecoder(MPAPI::Decoder *aDecoder);
|
||||
};
|
||||
|
||||
// Must be called on the main thread. Creates the plugin host if it doesn't
|
||||
// already exist.
|
||||
AndroidMediaPluginHost *EnsureAndroidMediaPluginHost();
|
||||
|
||||
// May be called on any thread after EnsureAndroidMediaPluginHost has been called.
|
||||
AndroidMediaPluginHost *GetAndroidMediaPluginHost();
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,449 +0,0 @@
|
|||
/* -*- 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 "AndroidMediaReader.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/gfx/Point.h"
|
||||
#include "MediaResource.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "AndroidMediaDecoder.h"
|
||||
#include "AndroidMediaPluginHost.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "VideoFrameContainer.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::media;
|
||||
|
||||
typedef mozilla::layers::Image Image;
|
||||
typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
|
||||
|
||||
AndroidMediaReader::AndroidMediaReader(AbstractMediaDecoder *aDecoder,
|
||||
const nsACString& aContentType) :
|
||||
MediaDecoderReader(aDecoder),
|
||||
mType(aContentType),
|
||||
mPlugin(nullptr),
|
||||
mHasAudio(false),
|
||||
mHasVideo(false),
|
||||
mVideoSeekTimeUs(-1),
|
||||
mAudioSeekTimeUs(-1)
|
||||
{
|
||||
}
|
||||
|
||||
nsresult AndroidMediaReader::ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
if (!mPlugin) {
|
||||
mPlugin = GetAndroidMediaPluginHost()->CreateDecoder(mDecoder->GetResource(), mType);
|
||||
if (!mPlugin) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the total duration (the max of the audio and video track).
|
||||
int64_t durationUs;
|
||||
mPlugin->GetDuration(mPlugin, &durationUs);
|
||||
if (durationUs) {
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(durationUs));
|
||||
}
|
||||
|
||||
if (mPlugin->HasVideo(mPlugin)) {
|
||||
int32_t width, height;
|
||||
mPlugin->GetVideoParameters(mPlugin, &width, &height);
|
||||
nsIntRect pictureRect(0, 0, width, height);
|
||||
|
||||
// Validate the container-reported frame and pictureRect sizes. This ensures
|
||||
// that our video frame creation code doesn't overflow.
|
||||
nsIntSize displaySize(width, height);
|
||||
nsIntSize frameSize(width, height);
|
||||
if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Video track's frame sizes will not overflow. Activate the video track.
|
||||
mHasVideo = true;
|
||||
mInfo.mVideo.mDisplay = displaySize;
|
||||
mPicture = pictureRect;
|
||||
mInitialFrame = frameSize;
|
||||
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
|
||||
if (container) {
|
||||
container->ClearCurrentFrame(IntSize(displaySize.width, displaySize.height));
|
||||
}
|
||||
}
|
||||
|
||||
if (mPlugin->HasAudio(mPlugin)) {
|
||||
int32_t numChannels, sampleRate;
|
||||
mPlugin->GetAudioParameters(mPlugin, &numChannels, &sampleRate);
|
||||
mHasAudio = true;
|
||||
mInfo.mAudio.mChannels = numChannels;
|
||||
mInfo.mAudio.mRate = sampleRate;
|
||||
}
|
||||
|
||||
*aInfo = mInfo;
|
||||
*aTags = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise>
|
||||
AndroidMediaReader::Shutdown()
|
||||
{
|
||||
ResetDecode();
|
||||
if (mPlugin) {
|
||||
GetAndroidMediaPluginHost()->DestroyDecoder(mPlugin);
|
||||
mPlugin = nullptr;
|
||||
}
|
||||
|
||||
return MediaDecoderReader::Shutdown();
|
||||
}
|
||||
|
||||
// Resets all state related to decoding, emptying all buffers etc.
|
||||
nsresult AndroidMediaReader::ResetDecode(TrackSet aTracks)
|
||||
{
|
||||
if (mLastVideoFrame) {
|
||||
mLastVideoFrame = nullptr;
|
||||
}
|
||||
mSeekRequest.DisconnectIfExists();
|
||||
mSeekPromise.RejectIfExists(NS_OK, __func__);
|
||||
return MediaDecoderReader::ResetDecode(aTracks);
|
||||
}
|
||||
|
||||
bool AndroidMediaReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
int64_t aTimeThreshold)
|
||||
{
|
||||
// Record number of frames decoded and parsed. Automatically update the
|
||||
// stats counters using the AutoNotifyDecoded stack-based class.
|
||||
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
|
||||
|
||||
// Throw away the currently buffered frame if we are seeking.
|
||||
if (mLastVideoFrame && mVideoSeekTimeUs != -1) {
|
||||
mLastVideoFrame = nullptr;
|
||||
}
|
||||
|
||||
ImageBufferCallback bufferCallback(mDecoder->GetImageContainer());
|
||||
RefPtr<Image> currentImage;
|
||||
|
||||
// Read next frame
|
||||
while (true) {
|
||||
MPAPI::VideoFrame frame;
|
||||
if (!mPlugin->ReadVideo(mPlugin, &frame, mVideoSeekTimeUs, &bufferCallback)) {
|
||||
// We reached the end of the video stream. If we have a buffered
|
||||
// video frame, push it the video queue using the total duration
|
||||
// of the video as the end time.
|
||||
if (mLastVideoFrame) {
|
||||
int64_t durationUs;
|
||||
mPlugin->GetDuration(mPlugin, &durationUs);
|
||||
durationUs = std::max<int64_t>(durationUs - mLastVideoFrame->mTime, 0);
|
||||
RefPtr<VideoData> data = VideoData::ShallowCopyUpdateDuration(mLastVideoFrame,
|
||||
durationUs);
|
||||
mVideoQueue.Push(data);
|
||||
mLastVideoFrame = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
mVideoSeekTimeUs = -1;
|
||||
|
||||
if (aKeyframeSkip) {
|
||||
// Disable keyframe skipping for now as
|
||||
// stagefright doesn't seem to be telling us
|
||||
// when a frame is a keyframe.
|
||||
#if 0
|
||||
if (!frame.mKeyFrame) {
|
||||
++a.mStats.mParsedFrames;
|
||||
++a.mStats.mDroppedFrames;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
aKeyframeSkip = false;
|
||||
}
|
||||
|
||||
if (frame.mSize == 0)
|
||||
return true;
|
||||
|
||||
currentImage = bufferCallback.GetImage();
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
IntRect picture = mPicture;
|
||||
|
||||
RefPtr<VideoData> v;
|
||||
if (currentImage) {
|
||||
gfx::IntSize frameSize = currentImage->GetSize();
|
||||
if (frameSize.width != mInitialFrame.width ||
|
||||
frameSize.height != mInitialFrame.height) {
|
||||
// Frame size is different from what the container reports. This is legal,
|
||||
// and we will preserve the ratio of the crop rectangle as it
|
||||
// was reported relative to the picture size reported by the container.
|
||||
picture.x = (mPicture.x * frameSize.width) / mInitialFrame.width;
|
||||
picture.y = (mPicture.y * frameSize.height) / mInitialFrame.height;
|
||||
picture.width = (frameSize.width * mPicture.width) / mInitialFrame.width;
|
||||
picture.height = (frameSize.height * mPicture.height) / mInitialFrame.height;
|
||||
}
|
||||
|
||||
v = VideoData::CreateFromImage(mInfo.mVideo,
|
||||
pos,
|
||||
frame.mTimeUs,
|
||||
1, // We don't know the duration yet.
|
||||
currentImage,
|
||||
frame.mKeyFrame,
|
||||
-1,
|
||||
picture);
|
||||
} else {
|
||||
// Assume YUV
|
||||
VideoData::YCbCrBuffer b;
|
||||
b.mPlanes[0].mData = static_cast<uint8_t *>(frame.Y.mData);
|
||||
b.mPlanes[0].mStride = frame.Y.mStride;
|
||||
b.mPlanes[0].mHeight = frame.Y.mHeight;
|
||||
b.mPlanes[0].mWidth = frame.Y.mWidth;
|
||||
b.mPlanes[0].mOffset = frame.Y.mOffset;
|
||||
b.mPlanes[0].mSkip = frame.Y.mSkip;
|
||||
|
||||
b.mPlanes[1].mData = static_cast<uint8_t *>(frame.Cb.mData);
|
||||
b.mPlanes[1].mStride = frame.Cb.mStride;
|
||||
b.mPlanes[1].mHeight = frame.Cb.mHeight;
|
||||
b.mPlanes[1].mWidth = frame.Cb.mWidth;
|
||||
b.mPlanes[1].mOffset = frame.Cb.mOffset;
|
||||
b.mPlanes[1].mSkip = frame.Cb.mSkip;
|
||||
|
||||
b.mPlanes[2].mData = static_cast<uint8_t *>(frame.Cr.mData);
|
||||
b.mPlanes[2].mStride = frame.Cr.mStride;
|
||||
b.mPlanes[2].mHeight = frame.Cr.mHeight;
|
||||
b.mPlanes[2].mWidth = frame.Cr.mWidth;
|
||||
b.mPlanes[2].mOffset = frame.Cr.mOffset;
|
||||
b.mPlanes[2].mSkip = frame.Cr.mSkip;
|
||||
|
||||
if (frame.Y.mWidth != mInitialFrame.width ||
|
||||
frame.Y.mHeight != mInitialFrame.height) {
|
||||
|
||||
// Frame size is different from what the container reports. This is legal,
|
||||
// and we will preserve the ratio of the crop rectangle as it
|
||||
// was reported relative to the picture size reported by the container.
|
||||
picture.x = (mPicture.x * frame.Y.mWidth) / mInitialFrame.width;
|
||||
picture.y = (mPicture.y * frame.Y.mHeight) / mInitialFrame.height;
|
||||
picture.width = (frame.Y.mWidth * mPicture.width) / mInitialFrame.width;
|
||||
picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
|
||||
}
|
||||
|
||||
// This is the approximate byte position in the stream.
|
||||
v = VideoData::CreateAndCopyData(mInfo.mVideo,
|
||||
mDecoder->GetImageContainer(),
|
||||
pos,
|
||||
frame.mTimeUs,
|
||||
1, // We don't know the duration yet.
|
||||
b,
|
||||
frame.mKeyFrame,
|
||||
-1,
|
||||
picture);
|
||||
}
|
||||
|
||||
if (!v) {
|
||||
return false;
|
||||
}
|
||||
a.mStats.mParsedFrames++;
|
||||
a.mStats.mDecodedFrames++;
|
||||
NS_ASSERTION(a.mStats.mDecodedFrames <= a.mStats.mParsedFrames, "Expect to decode fewer frames than parsed in AndroidMedia...");
|
||||
|
||||
// Since MPAPI doesn't give us the end time of frames, we keep one frame
|
||||
// buffered in AndroidMediaReader and push it into the queue as soon
|
||||
// we read the following frame so we can use that frame's start time as
|
||||
// the end time of the buffered frame.
|
||||
if (!mLastVideoFrame) {
|
||||
mLastVideoFrame = v;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the duration as the timestamp of the current frame minus the
|
||||
// timestamp of the previous frame. We can then return the previously
|
||||
// decoded frame, and it will have a valid timestamp.
|
||||
int64_t duration = v->mTime - mLastVideoFrame->mTime;
|
||||
mLastVideoFrame = VideoData::ShallowCopyUpdateDuration(mLastVideoFrame, duration);
|
||||
|
||||
// We have the start time of the next frame, so we can push the previous
|
||||
// frame into the queue, except if the end time is below the threshold,
|
||||
// in which case it wouldn't be displayed anyway.
|
||||
if (mLastVideoFrame->GetEndTime() < aTimeThreshold) {
|
||||
mLastVideoFrame = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Buffer the current frame we just decoded.
|
||||
mVideoQueue.Push(mLastVideoFrame);
|
||||
mLastVideoFrame = v;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AndroidMediaReader::DecodeAudioData()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
|
||||
// Read next frame
|
||||
MPAPI::AudioFrame source;
|
||||
if (!mPlugin->ReadAudio(mPlugin, &source, mAudioSeekTimeUs)) {
|
||||
return false;
|
||||
}
|
||||
mAudioSeekTimeUs = -1;
|
||||
|
||||
// Ignore empty buffers which stagefright media read will sporadically return
|
||||
if (source.mSize == 0)
|
||||
return true;
|
||||
|
||||
uint32_t frames = source.mSize / (source.mAudioChannels *
|
||||
sizeof(AudioDataValue));
|
||||
|
||||
typedef AudioCompactor::NativeCopy MPCopy;
|
||||
return mAudioCompactor.Push(pos,
|
||||
source.mTimeUs,
|
||||
source.mAudioSampleRate,
|
||||
frames,
|
||||
source.mAudioChannels,
|
||||
MPCopy(static_cast<uint8_t *>(source.mData),
|
||||
source.mSize,
|
||||
source.mAudioChannels));
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReader::SeekPromise>
|
||||
AndroidMediaReader::Seek(SeekTarget aTarget, int64_t aEndTime)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
RefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
|
||||
if (mHasAudio && mHasVideo) {
|
||||
// The decoder seeks/demuxes audio and video streams separately. So if
|
||||
// we seek both audio and video to aTarget, the audio stream can typically
|
||||
// seek closer to the seek target, since typically every audio block is
|
||||
// a sync point, whereas for video there are only keyframes once every few
|
||||
// seconds. So if we have both audio and video, we must seek the video
|
||||
// stream to the preceeding keyframe first, get the stream time, and then
|
||||
// seek the audio stream to match the video stream's time. Otherwise, the
|
||||
// audio and video streams won't be in sync after the seek.
|
||||
mVideoSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
|
||||
RefPtr<AndroidMediaReader> self = this;
|
||||
mSeekRequest.Begin(DecodeToFirstVideoData()->Then(OwnerThread(), __func__, [self] (MediaData* v) {
|
||||
self->mSeekRequest.Complete();
|
||||
self->mAudioSeekTimeUs = v->mTime;
|
||||
self->mSeekPromise.Resolve(media::TimeUnit::FromMicroseconds(self->mAudioSeekTimeUs), __func__);
|
||||
}, [self, aTarget] () {
|
||||
self->mSeekRequest.Complete();
|
||||
self->mAudioSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
self->mSeekPromise.Resolve(aTarget.GetTime(), __func__);
|
||||
}));
|
||||
} else {
|
||||
mAudioSeekTimeUs = mVideoSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
mSeekPromise.Resolve(aTarget.GetTime(), __func__);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
AndroidMediaReader::ImageBufferCallback::ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer) :
|
||||
mImageContainer(aImageContainer)
|
||||
{
|
||||
}
|
||||
|
||||
void *
|
||||
AndroidMediaReader::ImageBufferCallback::operator()(size_t aWidth, size_t aHeight,
|
||||
MPAPI::ColorFormat aColorFormat)
|
||||
{
|
||||
if (!mImageContainer) {
|
||||
NS_WARNING("No image container to construct an image");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Image> image;
|
||||
switch(aColorFormat) {
|
||||
case MPAPI::RGB565:
|
||||
image = mozilla::layers::CreateSharedRGBImage(mImageContainer,
|
||||
nsIntSize(aWidth, aHeight),
|
||||
SurfaceFormat::R5G6B5_UINT16);
|
||||
if (!image) {
|
||||
NS_WARNING("Could not create rgb image");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mImage = image;
|
||||
return image->GetBuffer();
|
||||
case MPAPI::I420:
|
||||
return CreateI420Image(aWidth, aHeight);
|
||||
default:
|
||||
NS_NOTREACHED("Color format not supported");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
AndroidMediaReader::ImageBufferCallback::CreateI420Image(size_t aWidth,
|
||||
size_t aHeight)
|
||||
{
|
||||
RefPtr<PlanarYCbCrImage> yuvImage = mImageContainer->CreatePlanarYCbCrImage();
|
||||
mImage = yuvImage;
|
||||
|
||||
if (!yuvImage) {
|
||||
NS_WARNING("Could not create I420 image");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use uint32_t throughout to match AllocateAndGetNewBuffer's param
|
||||
const auto checkedFrameSize =
|
||||
CheckedInt<uint32_t>(aWidth) * aHeight;
|
||||
|
||||
// Allocate enough for one full resolution Y plane
|
||||
// and two quarter resolution Cb/Cr planes.
|
||||
const auto checkedBufferSize =
|
||||
checkedFrameSize + checkedFrameSize / 2;
|
||||
|
||||
if (!checkedBufferSize.isValid()) { // checks checkedFrameSize too
|
||||
NS_WARNING("Could not create I420 image");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto frameSize = checkedFrameSize.value();
|
||||
|
||||
uint8_t *buffer =
|
||||
yuvImage->AllocateAndGetNewBuffer(checkedBufferSize.value());
|
||||
|
||||
mozilla::layers::PlanarYCbCrData frameDesc;
|
||||
|
||||
frameDesc.mYChannel = buffer;
|
||||
frameDesc.mCbChannel = buffer + frameSize;
|
||||
frameDesc.mCrChannel = frameDesc.mCbChannel + frameSize / 4;
|
||||
|
||||
frameDesc.mYSize = IntSize(aWidth, aHeight);
|
||||
frameDesc.mCbCrSize = IntSize(aWidth / 2, aHeight / 2);
|
||||
|
||||
frameDesc.mYStride = aWidth;
|
||||
frameDesc.mCbCrStride = aWidth / 2;
|
||||
|
||||
frameDesc.mYSkip = 0;
|
||||
frameDesc.mCbSkip = 0;
|
||||
frameDesc.mCrSkip = 0;
|
||||
|
||||
frameDesc.mPicX = 0;
|
||||
frameDesc.mPicY = 0;
|
||||
frameDesc.mPicSize = IntSize(aWidth, aHeight);
|
||||
|
||||
yuvImage->AdoptData(frameDesc);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
already_AddRefed<Image>
|
||||
AndroidMediaReader::ImageBufferCallback::GetImage()
|
||||
{
|
||||
return mImage.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/* -*- 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(AndroidMediaReader_h_)
|
||||
#define AndroidMediaReader_h_
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "mozilla/layers/SharedRGBImage.h"
|
||||
|
||||
#include "MPAPI.h"
|
||||
|
||||
class nsACString;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AbstractMediaDecoder;
|
||||
|
||||
namespace layers {
|
||||
class ImageContainer;
|
||||
}
|
||||
|
||||
class AndroidMediaReader : public MediaDecoderReader
|
||||
{
|
||||
nsCString mType;
|
||||
MPAPI::Decoder *mPlugin;
|
||||
bool mHasAudio;
|
||||
bool mHasVideo;
|
||||
nsIntRect mPicture;
|
||||
nsIntSize mInitialFrame;
|
||||
int64_t mVideoSeekTimeUs;
|
||||
int64_t mAudioSeekTimeUs;
|
||||
RefPtr<VideoData> mLastVideoFrame;
|
||||
MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
|
||||
MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
|
||||
public:
|
||||
AndroidMediaReader(AbstractMediaDecoder* aDecoder,
|
||||
const nsACString& aContentType);
|
||||
|
||||
nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
|
||||
TrackInfo::kVideoTrack)) override;
|
||||
|
||||
bool DecodeAudioData() override;
|
||||
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
|
||||
|
||||
nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
|
||||
RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) override;
|
||||
|
||||
RefPtr<ShutdownPromise> Shutdown() override;
|
||||
|
||||
class ImageBufferCallback : public MPAPI::BufferCallback {
|
||||
typedef mozilla::layers::Image Image;
|
||||
|
||||
public:
|
||||
ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer);
|
||||
void *operator()(size_t aWidth, size_t aHeight,
|
||||
MPAPI::ColorFormat aColorFormat) override;
|
||||
already_AddRefed<Image> GetImage();
|
||||
|
||||
private:
|
||||
uint8_t *CreateI420Image(size_t aWidth, size_t aHeight);
|
||||
|
||||
mozilla::layers::ImageContainer *mImageContainer;
|
||||
RefPtr<Image> mImage;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,503 +0,0 @@
|
|||
/* -*- 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 "mozilla/Assertions.h"
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsISocketTransport.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRandomGenerator.h"
|
||||
#include "nsReadLine.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "MediaResource.h"
|
||||
#include "AndroidMediaResourceServer.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define strtoll _strtoi64
|
||||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
/*
|
||||
ReadCRLF is a variant of NS_ReadLine from nsReadLine.h that deals
|
||||
with the carriage return/line feed requirements of HTTP requests.
|
||||
*/
|
||||
template<typename CharT, class StreamType, class StringType>
|
||||
nsresult
|
||||
ReadCRLF (StreamType* aStream, nsLineBuffer<CharT> * aBuffer,
|
||||
StringType & aLine, bool *aMore)
|
||||
{
|
||||
// eollast is true if the last character in the buffer is a '\r',
|
||||
// signaling a potential '\r\n' sequence split between reads.
|
||||
bool eollast = false;
|
||||
|
||||
aLine.Truncate();
|
||||
|
||||
while (1) { // will be returning out of this loop on eol or eof
|
||||
if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it.
|
||||
uint32_t bytesRead;
|
||||
nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
|
||||
if (NS_FAILED(rv) || bytesRead == 0) {
|
||||
*aMore = false;
|
||||
return rv;
|
||||
}
|
||||
aBuffer->start = aBuffer->buf;
|
||||
aBuffer->end = aBuffer->buf + bytesRead;
|
||||
*(aBuffer->end) = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk the buffer looking for an end-of-line.
|
||||
* There are 4 cases to consider:
|
||||
* 1. the CR char is the last char in the buffer
|
||||
* 2. the CRLF sequence are the last characters in the buffer
|
||||
* 3. the CRLF sequence + one or more chars at the end of the buffer
|
||||
* we need at least one char after the first CRLF sequence to
|
||||
* set |aMore| correctly.
|
||||
* 4. The LF character is the first char in the buffer when eollast is
|
||||
* true.
|
||||
*/
|
||||
CharT* current = aBuffer->start;
|
||||
if (eollast) { // Case 4
|
||||
if (*current == '\n') {
|
||||
aBuffer->start = ++current;
|
||||
*aMore = true;
|
||||
return NS_OK;
|
||||
}
|
||||
else {
|
||||
eollast = false;
|
||||
aLine.Append('\r');
|
||||
}
|
||||
}
|
||||
// Cases 2 and 3
|
||||
for ( ; current < aBuffer->end-1; ++current) {
|
||||
if (*current == '\r' && *(current+1) == '\n') {
|
||||
*current++ = '\0';
|
||||
*current++ = '\0';
|
||||
aLine.Append(aBuffer->start);
|
||||
aBuffer->start = current;
|
||||
*aMore = true;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Case 1
|
||||
if (*current == '\r') {
|
||||
eollast = true;
|
||||
*current++ = '\0';
|
||||
}
|
||||
|
||||
aLine.Append(aBuffer->start);
|
||||
aBuffer->start = aBuffer->end; // mark the buffer empty
|
||||
}
|
||||
}
|
||||
|
||||
// Each client HTTP request results in a thread being spawned to process it.
|
||||
// That thread has a single event dispatched to it which handles the HTTP
|
||||
// protocol. It parses the headers and forwards data from the MediaResource
|
||||
// associated with the URL back to client. When the request is complete it will
|
||||
// shutdown the thread.
|
||||
class ServeResourceEvent : public Runnable {
|
||||
private:
|
||||
// Reading from this reads the data sent from the client.
|
||||
nsCOMPtr<nsIInputStream> mInput;
|
||||
|
||||
// Writing to this sends data to the client.
|
||||
nsCOMPtr<nsIOutputStream> mOutput;
|
||||
|
||||
// The AndroidMediaResourceServer that owns the MediaResource instances
|
||||
// served. This is used to lookup the MediaResource from the URL.
|
||||
RefPtr<AndroidMediaResourceServer> mServer;
|
||||
|
||||
// Write 'aBufferLength' bytes from 'aBuffer' to 'mOutput'. This
|
||||
// method ensures all the data is written by checking the number
|
||||
// of bytes returned from the output streams 'Write' method and
|
||||
// looping until done.
|
||||
nsresult WriteAll(char const* aBuffer, int32_t aBufferLength);
|
||||
|
||||
public:
|
||||
ServeResourceEvent(nsIInputStream* aInput, nsIOutputStream* aOutput,
|
||||
AndroidMediaResourceServer* aServer)
|
||||
: mInput(aInput), mOutput(aOutput), mServer(aServer) {}
|
||||
|
||||
// This method runs on the thread and exits when it has completed the
|
||||
// HTTP request.
|
||||
NS_IMETHOD Run();
|
||||
|
||||
// Given the first line of an HTTP request, parse the URL requested and
|
||||
// return the MediaResource for that URL.
|
||||
already_AddRefed<MediaResource> GetMediaResource(nsCString const& aHTTPRequest);
|
||||
|
||||
// Gracefully shutdown the thread and cleanup resources
|
||||
void Shutdown();
|
||||
};
|
||||
|
||||
nsresult
|
||||
ServeResourceEvent::WriteAll(char const* aBuffer, int32_t aBufferLength)
|
||||
{
|
||||
while (aBufferLength > 0) {
|
||||
uint32_t written = 0;
|
||||
nsresult rv = mOutput->Write(aBuffer, aBufferLength, &written);
|
||||
if (NS_FAILED (rv)) return rv;
|
||||
|
||||
aBufferLength -= written;
|
||||
aBuffer += written;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<MediaResource>
|
||||
ServeResourceEvent::GetMediaResource(nsCString const& aHTTPRequest)
|
||||
{
|
||||
// Check that the HTTP method is GET
|
||||
const char* HTTP_METHOD = "GET ";
|
||||
if (strncmp(aHTTPRequest.get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* url_start = strchr(aHTTPRequest.get(), ' ');
|
||||
if (!url_start) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* url_end = strrchr(++url_start, ' ');
|
||||
if (!url_end) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The path extracted from the HTTP request is used as a key in hash
|
||||
// table. It is not related to retrieving data from the filesystem so
|
||||
// we don't need to do any sanity checking on ".." paths and similar
|
||||
// exploits.
|
||||
nsCString relative(url_start, url_end - url_start);
|
||||
RefPtr<MediaResource> resource =
|
||||
mServer->GetResource(mServer->GetURLPrefix() + relative);
|
||||
return resource.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ServeResourceEvent::Run() {
|
||||
bool more = false; // Are there HTTP headers to read after the first line
|
||||
nsCString line; // Contains the current line read from input stream
|
||||
nsLineBuffer<char>* buffer = new nsLineBuffer<char>();
|
||||
nsresult rv = ReadCRLF(mInput.get(), buffer, line, &more);
|
||||
if (NS_FAILED(rv)) { Shutdown(); return rv; }
|
||||
|
||||
// First line contains the HTTP GET request. Extract the URL and obtain
|
||||
// the MediaResource for it.
|
||||
RefPtr<MediaResource> resource = GetMediaResource(line);
|
||||
if (!resource) {
|
||||
const char* response_404 = "HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Length: 0\r\n\r\n";
|
||||
rv = WriteAll(response_404, strlen(response_404));
|
||||
Shutdown();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Offset in bytes to start reading from resource.
|
||||
// This is zero by default but can be set to another starting value if
|
||||
// this HTTP request includes a byte range request header.
|
||||
int64_t start = 0;
|
||||
|
||||
// Keep reading lines until we get a zero length line, which is the HTTP
|
||||
// protocol's way of signifying the end of headers and start of body, or
|
||||
// until we have no more data to read.
|
||||
while (more && line.Length() > 0) {
|
||||
rv = ReadCRLF(mInput.get(), buffer, line, &more);
|
||||
if (NS_FAILED(rv)) { Shutdown(); return rv; }
|
||||
|
||||
// Look for a byte range request header. If there is one, set the
|
||||
// media resource offset to start from to that requested. Here we
|
||||
// only check for the range request format used by Android rather
|
||||
// than implementing all possibilities in the HTTP specification.
|
||||
// That is, the range request is of the form:
|
||||
// Range: bytes=nnnn-
|
||||
// Were 'nnnn' is an integer number.
|
||||
// The end of the range is not checked, instead we return up to
|
||||
// the end of the resource and the client is informed of this via
|
||||
// the content-range header.
|
||||
NS_NAMED_LITERAL_CSTRING(byteRange, "Range: bytes=");
|
||||
const char* s = strstr(line.get(), byteRange.get());
|
||||
if (s) {
|
||||
start = strtoll(s+byteRange.Length(), nullptr, 10);
|
||||
|
||||
// Clamp 'start' to be between 0 and the resource length.
|
||||
start = std::max(int64_t(0), std::min(resource->GetLength(), start));
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP response to use if this is a non byte range request
|
||||
const char* response_normal = "HTTP/1.1 200 OK\r\n";
|
||||
|
||||
// HTTP response to use if this is a byte range request
|
||||
const char* response_range = "HTTP/1.1 206 Partial Content\r\n";
|
||||
|
||||
// End of HTTP reponse headers is indicated by an empty line.
|
||||
const char* response_end = "\r\n";
|
||||
|
||||
// If the request was a byte range request, we need to read from the
|
||||
// requested offset. If the resource is non-seekable, or the seek
|
||||
// fails, then the start offset is set back to zero. This results in all
|
||||
// HTTP response data being as if the byte range request was not made.
|
||||
if (start > 0 && !resource->IsTransportSeekable()) {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
const char* response_line = start > 0 ?
|
||||
response_range :
|
||||
response_normal;
|
||||
rv = WriteAll(response_line, strlen(response_line));
|
||||
if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
|
||||
|
||||
// Buffer used for reading from the input stream and writing to
|
||||
// the output stream. The buffer size should be big enough for the
|
||||
// HTTP response headers sent below. A static_assert ensures
|
||||
// this where the buffer is used.
|
||||
const int buffer_size = 32768;
|
||||
auto b = MakeUnique<char[]>(buffer_size);
|
||||
|
||||
// If we know the length of the resource, send a Content-Length header.
|
||||
int64_t contentlength = resource->GetLength() - start;
|
||||
if (contentlength > 0) {
|
||||
static_assert (buffer_size > 1024,
|
||||
"buffer_size must be large enough "
|
||||
"to hold response headers");
|
||||
snprintf(b.get(), buffer_size, "Content-Length: %" PRId64 "\r\n", contentlength);
|
||||
rv = WriteAll(b.get(), strlen(b.get()));
|
||||
if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
|
||||
}
|
||||
|
||||
// If the request was a byte range request, respond with a Content-Range
|
||||
// header which details the extent of the data returned.
|
||||
if (start > 0) {
|
||||
static_assert (buffer_size > 1024,
|
||||
"buffer_size must be large enough "
|
||||
"to hold response headers");
|
||||
snprintf(b.get(), buffer_size, "Content-Range: "
|
||||
"bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n",
|
||||
start, resource->GetLength() - 1, resource->GetLength());
|
||||
rv = WriteAll(b.get(), strlen(b.get()));
|
||||
if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
|
||||
}
|
||||
|
||||
rv = WriteAll(response_end, strlen(response_end));
|
||||
if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
|
||||
|
||||
rv = mOutput->Flush();
|
||||
if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
|
||||
|
||||
// Read data from media resource
|
||||
uint32_t bytesRead = 0; // Number of bytes read/written to streams
|
||||
rv = resource->ReadAt(start, b.get(), buffer_size, &bytesRead);
|
||||
while (NS_SUCCEEDED(rv) && bytesRead != 0) {
|
||||
// Keep track of what we think the starting position for the next read
|
||||
// is. This is used in subsequent ReadAt calls to ensure we are reading
|
||||
// from the correct offset in the case where another thread is reading
|
||||
// from th same MediaResource.
|
||||
start += bytesRead;
|
||||
|
||||
// Write data obtained from media resource to output stream
|
||||
rv = WriteAll(b.get(), bytesRead);
|
||||
if (NS_FAILED (rv)) break;
|
||||
|
||||
rv = resource->ReadAt(start, b.get(), 32768, &bytesRead);
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ServeResourceEvent::Shutdown()
|
||||
{
|
||||
// Cleanup resources and exit.
|
||||
mInput->Close();
|
||||
mOutput->Close();
|
||||
|
||||
// To shutdown the current thread we need to first exit this event.
|
||||
// The Shutdown event below is posted to the main thread to do this.
|
||||
nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(NS_GetCurrentThread());
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
/*
|
||||
This is the listener attached to the server socket. When an HTTP
|
||||
request is made by the client the OnSocketAccepted method is
|
||||
called. This method will spawn a thread to process the request.
|
||||
The thread receives a single event which does the parsing of
|
||||
the HTTP request and forwarding the data from the MediaResource
|
||||
to the output stream of the request.
|
||||
|
||||
The MediaResource used for providing the request data is obtained
|
||||
from the AndroidMediaResourceServer that created this listener, using the
|
||||
URL the client requested.
|
||||
*/
|
||||
class ResourceSocketListener : public nsIServerSocketListener
|
||||
{
|
||||
public:
|
||||
// The AndroidMediaResourceServer used to look up the MediaResource
|
||||
// on requests.
|
||||
RefPtr<AndroidMediaResourceServer> mServer;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSISERVERSOCKETLISTENER
|
||||
|
||||
ResourceSocketListener(AndroidMediaResourceServer* aServer) :
|
||||
mServer(aServer)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ResourceSocketListener() { }
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ResourceSocketListener, nsIServerSocketListener)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ResourceSocketListener::OnSocketAccepted(nsIServerSocket* aServ,
|
||||
nsISocketTransport* aTrans)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> input;
|
||||
nsCOMPtr<nsIOutputStream> output;
|
||||
nsresult rv;
|
||||
|
||||
rv = aTrans->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(input));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = aTrans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(output));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
rv = NS_NewThread(getter_AddRefs(thread));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new ServeResourceEvent(input.get(), output.get(), mServer);
|
||||
return thread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ResourceSocketListener::OnStopListening(nsIServerSocket* aServ, nsresult aStatus)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AndroidMediaResourceServer::AndroidMediaResourceServer() :
|
||||
mMutex("AndroidMediaResourceServer")
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
AndroidMediaResourceServer::Run()
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsresult rv;
|
||||
mSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = mSocket->InitSpecialConnection(-1,
|
||||
nsIServerSocket::LoopbackOnly
|
||||
| nsIServerSocket::KeepWhenOffline,
|
||||
-1);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = mSocket->AsyncListen(new ResourceSocketListener(this));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<AndroidMediaResourceServer>
|
||||
AndroidMediaResourceServer::Start()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RefPtr<AndroidMediaResourceServer> server = new AndroidMediaResourceServer();
|
||||
server->Run();
|
||||
return server.forget();
|
||||
}
|
||||
|
||||
void
|
||||
AndroidMediaResourceServer::Stop()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mSocket->Close();
|
||||
mSocket = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
AndroidMediaResourceServer::AppendRandomPath(nsCString& aUrl)
|
||||
{
|
||||
// Use a cryptographic quality PRNG to generate raw random bytes
|
||||
// and convert that to a base64 string for use as an URL path. This
|
||||
// is based on code from nsExternalAppHandler::SetUpTempFile.
|
||||
nsresult rv;
|
||||
nsAutoCString salt;
|
||||
rv = GenerateRandomPathName(salt, 16);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
aUrl += "/";
|
||||
aUrl += salt;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
AndroidMediaResourceServer::AddResource(mozilla::MediaResource* aResource, nsCString& aUrl)
|
||||
{
|
||||
nsCString url = GetURLPrefix();
|
||||
nsresult rv = AppendRandomPath(url);
|
||||
if (NS_FAILED (rv)) return rv;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// Adding a resource URL that already exists is considered an error.
|
||||
if (mResources.find(url) != mResources.end()) return NS_ERROR_FAILURE;
|
||||
mResources[url] = aResource;
|
||||
}
|
||||
|
||||
aUrl = url;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
AndroidMediaResourceServer::RemoveResource(nsCString const& aUrl)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mResources.erase(aUrl);
|
||||
}
|
||||
|
||||
nsCString
|
||||
AndroidMediaResourceServer::GetURLPrefix()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
int32_t port = 0;
|
||||
nsresult rv = mSocket->GetPort(&port);
|
||||
if (NS_FAILED (rv) || port < 0) {
|
||||
return nsCString("");
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%d", port >= 0 ? port : 0);
|
||||
return nsCString(buffer);
|
||||
}
|
||||
|
||||
already_AddRefed<MediaResource>
|
||||
AndroidMediaResourceServer::GetResource(nsCString const& aUrl)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
ResourceMap::const_iterator it = mResources.find(aUrl);
|
||||
if (it == mResources.end()) return nullptr;
|
||||
|
||||
RefPtr<MediaResource> resource = it->second;
|
||||
return resource.forget();
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/* -*- 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(AndroidMediaResourceServer_h_)
|
||||
#define AndroidMediaResourceServer_h_
|
||||
|
||||
#include <map>
|
||||
#include "nsIServerSocket.h"
|
||||
#include "MediaResource.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MediaResource;
|
||||
|
||||
/*
|
||||
AndroidMediaResourceServer instantiates a socket server that understands
|
||||
HTTP requests for MediaResource instances. The server runs on an
|
||||
automatically selected port and MediaResource instances are registered.
|
||||
The registration returns a string URL than can be used to fetch the
|
||||
resource. That URL contains a randomly generated path to make it
|
||||
difficult for other local applications on the device to guess it.
|
||||
|
||||
The HTTP protocol is limited in that it supports only what the
|
||||
Android DataSource implementation uses to fetch media. It
|
||||
understands HTTP GET and byte range requests.
|
||||
|
||||
The intent of this class is to be used in Media backends that
|
||||
have a system component that does its own network requests. These
|
||||
requests are made against this server which then uses standard
|
||||
Gecko network requests and media cache usage.
|
||||
|
||||
The AndroidMediaResourceServer can be instantiated on any thread and
|
||||
its methods are threadsafe - they can be called on any thread.
|
||||
The server socket itself is always run on the main thread and
|
||||
this is done by the Start() static method by synchronously
|
||||
dispatching to the main thread.
|
||||
*/
|
||||
class AndroidMediaResourceServer : public Runnable
|
||||
{
|
||||
private:
|
||||
// Mutex protecting private members of AndroidMediaResourceServer.
|
||||
// All member variables below this point in the class definition
|
||||
// must acquire the mutex before access.
|
||||
mozilla::Mutex mMutex;
|
||||
|
||||
// Server socket used to listen for incoming connections
|
||||
nsCOMPtr<nsIServerSocket> mSocket;
|
||||
|
||||
// Mapping between MediaResource URL's to the MediaResource
|
||||
// object served at that URL.
|
||||
typedef std::map<nsCString,
|
||||
RefPtr<mozilla::MediaResource> > ResourceMap;
|
||||
ResourceMap mResources;
|
||||
|
||||
// Create a AndroidMediaResourceServer that will listen on an automatically
|
||||
// selected port when started. This is private as it should only be
|
||||
// called internally from the public 'Start' method.
|
||||
AndroidMediaResourceServer();
|
||||
NS_IMETHOD Run();
|
||||
|
||||
// Append a random URL path to a string. This is used for creating a
|
||||
// unique URl for a resource which helps prevent malicious software
|
||||
// running on the same machine as the server from guessing the URL
|
||||
// and accessing video data.
|
||||
nsresult AppendRandomPath(nsCString& aURL);
|
||||
|
||||
public:
|
||||
// Create a AndroidMediaResourceServer and start it listening. This call will
|
||||
// perform a synchronous request on the main thread.
|
||||
static already_AddRefed<AndroidMediaResourceServer> Start();
|
||||
|
||||
// Stops the server from listening and accepting further connections.
|
||||
void Stop();
|
||||
|
||||
// Add a MediaResource to be served by this server. Stores the
|
||||
// absolute URL that can be used to access the resource in 'aUrl'.
|
||||
nsresult AddResource(mozilla::MediaResource* aResource, nsCString& aUrl);
|
||||
|
||||
// Remove a MediaResource so it is no longer served by this server.
|
||||
// The URL provided must match exactly that provided by a previous
|
||||
// call to "AddResource".
|
||||
void RemoveResource(nsCString const& aUrl);
|
||||
|
||||
// Returns the prefix for HTTP requests to the server. This plus
|
||||
// the result of AddResource results in an Absolute URL.
|
||||
nsCString GetURLPrefix();
|
||||
|
||||
// Returns the resource asociated with a given URL
|
||||
already_AddRefed<mozilla::MediaResource> GetResource(nsCString const& aUrl);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
/* -*- 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(MPAPI_h_)
|
||||
#define MPAPI_h_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MPAPI {
|
||||
|
||||
enum ColorFormat {
|
||||
I420,
|
||||
RGB565
|
||||
};
|
||||
|
||||
/*
|
||||
* A callback for the plugin to use to request a buffer owned by gecko. This can
|
||||
* save us a copy or two down the line.
|
||||
*/
|
||||
class BufferCallback {
|
||||
public:
|
||||
virtual void *operator()(size_t aWidth, size_t aHeight,
|
||||
ColorFormat aColorFormat) = 0;
|
||||
};
|
||||
|
||||
struct VideoPlane {
|
||||
VideoPlane() :
|
||||
mData(0),
|
||||
mStride(0),
|
||||
mWidth(0),
|
||||
mHeight(0),
|
||||
mOffset(0),
|
||||
mSkip(0)
|
||||
{}
|
||||
|
||||
void *mData;
|
||||
int32_t mStride;
|
||||
int32_t mWidth;
|
||||
int32_t mHeight;
|
||||
int32_t mOffset;
|
||||
int32_t mSkip;
|
||||
};
|
||||
|
||||
struct VideoFrame {
|
||||
int64_t mTimeUs;
|
||||
bool mKeyFrame;
|
||||
void *mData;
|
||||
size_t mSize;
|
||||
int32_t mStride;
|
||||
int32_t mSliceHeight;
|
||||
int32_t mRotation;
|
||||
VideoPlane Y;
|
||||
VideoPlane Cb;
|
||||
VideoPlane Cr;
|
||||
|
||||
VideoFrame() :
|
||||
mTimeUs(0),
|
||||
mKeyFrame(false),
|
||||
mData(0),
|
||||
mSize(0),
|
||||
mStride(0),
|
||||
mSliceHeight(0),
|
||||
mRotation(0)
|
||||
{}
|
||||
|
||||
void Set(int64_t aTimeUs, bool aKeyFrame,
|
||||
void *aData, size_t aSize, int32_t aStride, int32_t aSliceHeight, int32_t aRotation,
|
||||
void *aYData, int32_t aYStride, int32_t aYWidth, int32_t aYHeight, int32_t aYOffset, int32_t aYSkip,
|
||||
void *aCbData, int32_t aCbStride, int32_t aCbWidth, int32_t aCbHeight, int32_t aCbOffset, int32_t aCbSkip,
|
||||
void *aCrData, int32_t aCrStride, int32_t aCrWidth, int32_t aCrHeight, int32_t aCrOffset, int32_t aCrSkip)
|
||||
{
|
||||
mTimeUs = aTimeUs;
|
||||
mKeyFrame = aKeyFrame;
|
||||
mData = aData;
|
||||
mSize = aSize;
|
||||
mStride = aStride;
|
||||
mSliceHeight = aSliceHeight;
|
||||
mRotation = aRotation;
|
||||
Y.mData = aYData;
|
||||
Y.mStride = aYStride;
|
||||
Y.mWidth = aYWidth;
|
||||
Y.mHeight = aYHeight;
|
||||
Y.mOffset = aYOffset;
|
||||
Y.mSkip = aYSkip;
|
||||
Cb.mData = aCbData;
|
||||
Cb.mStride = aCbStride;
|
||||
Cb.mWidth = aCbWidth;
|
||||
Cb.mHeight = aCbHeight;
|
||||
Cb.mOffset = aCbOffset;
|
||||
Cb.mSkip = aCbSkip;
|
||||
Cr.mData = aCrData;
|
||||
Cr.mStride = aCrStride;
|
||||
Cr.mWidth = aCrWidth;
|
||||
Cr.mHeight = aCrHeight;
|
||||
Cr.mOffset = aCrOffset;
|
||||
Cr.mSkip = aCrSkip;
|
||||
}
|
||||
};
|
||||
|
||||
struct AudioFrame {
|
||||
int64_t mTimeUs;
|
||||
void *mData; // 16PCM interleaved
|
||||
size_t mSize; // Size of mData in bytes
|
||||
int32_t mAudioChannels;
|
||||
int32_t mAudioSampleRate;
|
||||
|
||||
AudioFrame() :
|
||||
mTimeUs(0),
|
||||
mData(0),
|
||||
mSize(0),
|
||||
mAudioChannels(0),
|
||||
mAudioSampleRate(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Set(int64_t aTimeUs,
|
||||
void *aData, size_t aSize,
|
||||
int32_t aAudioChannels, int32_t aAudioSampleRate)
|
||||
{
|
||||
mTimeUs = aTimeUs;
|
||||
mData = aData;
|
||||
mSize = aSize;
|
||||
mAudioChannels = aAudioChannels;
|
||||
mAudioSampleRate = aAudioSampleRate;
|
||||
}
|
||||
};
|
||||
|
||||
struct Decoder;
|
||||
|
||||
struct PluginHost {
|
||||
bool (*Read)(Decoder *aDecoder, char *aBuffer, int64_t aOffset, uint32_t aCount, uint32_t* aBytes);
|
||||
uint64_t (*GetLength)(Decoder *aDecoder);
|
||||
void (*SetMetaDataReadMode)(Decoder *aDecoder);
|
||||
void (*SetPlaybackReadMode)(Decoder *aDecoder);
|
||||
bool (*GetIntPref)(const char *aPref, int32_t *aResult);
|
||||
bool (*GetSystemInfoString)(const char *aKey, char *aResult, size_t aResultLen);
|
||||
};
|
||||
|
||||
struct Decoder {
|
||||
void *mResource;
|
||||
void *mPrivate;
|
||||
|
||||
Decoder();
|
||||
|
||||
void (*GetDuration)(Decoder *aDecoder, int64_t *durationUs);
|
||||
void (*GetVideoParameters)(Decoder *aDecoder, int32_t *aWidth, int32_t *aHeight);
|
||||
void (*GetAudioParameters)(Decoder *aDecoder, int32_t *aNumChannels, int32_t *aSampleRate);
|
||||
bool (*HasVideo)(Decoder *aDecoder);
|
||||
bool (*HasAudio)(Decoder *aDecoder);
|
||||
bool (*ReadVideo)(Decoder *aDecoder, VideoFrame *aFrame, int64_t aSeekTimeUs, BufferCallback *aBufferCallback);
|
||||
bool (*ReadAudio)(Decoder *aDecoder, AudioFrame *aFrame, int64_t aSeekTimeUs);
|
||||
void (*DestroyDecoder)(Decoder *);
|
||||
};
|
||||
|
||||
struct Manifest {
|
||||
bool (*CanDecode)(const char *aMimeChars, size_t aMimeLen, const char* const**aCodecs);
|
||||
bool (*CreateDecoder)(PluginHost *aPluginHost, Decoder *aDecoder,
|
||||
const char *aMimeChars, size_t aMimeLen);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# -*- 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'AndroidMediaDecoder.h',
|
||||
'AndroidMediaPluginHost.h',
|
||||
'AndroidMediaReader.h',
|
||||
'AndroidMediaResourceServer.h',
|
||||
'MPAPI.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AndroidMediaDecoder.cpp',
|
||||
'AndroidMediaPluginHost.cpp',
|
||||
'AndroidMediaReader.cpp',
|
||||
'AndroidMediaResourceServer.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/html',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
@ -43,9 +43,6 @@ DIRS += [
|
|||
'standalone',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ANDROID_OMX']:
|
||||
DIRS += ['android']
|
||||
|
||||
if CONFIG['MOZ_FMP4']:
|
||||
DIRS += ['fmp4']
|
||||
|
||||
|
|
|
|||
|
|
@ -92,10 +92,6 @@
|
|||
#include "nsSynthVoiceRegistry.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
#include "AndroidMediaPluginHost.h"
|
||||
#endif
|
||||
|
||||
#include "CubebUtils.h"
|
||||
#include "Latency.h"
|
||||
#include "WebAudioUtils.h"
|
||||
|
|
@ -381,11 +377,6 @@ nsLayoutStatics::Shutdown()
|
|||
nsAutoCopyListener::Shutdown();
|
||||
FrameLayerBuilder::Shutdown();
|
||||
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
AndroidMediaPluginHost::Shutdown();
|
||||
#endif
|
||||
|
||||
CubebUtils::ShutdownLibrary();
|
||||
AsyncLatencyLogger::ShutdownLogger();
|
||||
WebAudioUtils::Shutdown();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue