Merge autoland to central, a=merge

This commit is contained in:
Wes Kocher 2016-11-07 13:57:04 -08:00
Родитель d78b4ced28 fea88a9ded
Коммит 0dc89fa8e1
27 изменённых файлов: 690 добавлений и 139 удалений

Просмотреть файл

@ -0,0 +1,44 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
function getRecentlyClosed(maxResults, extension) {
let recentlyClosed = [];
// Get closed windows
let closedWindowData = SessionStore.getClosedWindowData(false);
for (let window of closedWindowData) {
recentlyClosed.push({
lastModified: window.closedAt,
window: WindowManager.convertFromSessionStoreClosedData(window, extension)});
}
// Get closed tabs
for (let window of WindowListManager.browserWindows()) {
let closedTabData = SessionStore.getClosedTabData(window, false);
for (let tab of closedTabData) {
recentlyClosed.push({
lastModified: tab.closedAt,
tab: TabManager.for(extension).convertFromSessionStoreClosedData(tab, window)});
}
}
// Sort windows and tabs
recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
return recentlyClosed.slice(0, maxResults);
}
extensions.registerSchemaAPI("sessions", "addon_parent", context => {
let {extension} = context;
return {
sessions: {
getRecentlyClosed: function(filter) {
let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
return Promise.resolve(getRecentlyClosed(maxResults, extension));
},
},
};
});

Просмотреть файл

@ -665,6 +665,23 @@ ExtensionTabManager.prototype = {
return result;
},
// Converts tabs returned from SessionStore.getClosedTabData and
// SessionStore.getClosedWindowData into API tab objects
convertFromSessionStoreClosedData(tab, window) {
let result = {
sessionId: String(tab.closedId),
index: tab.pos ? tab.pos : 0,
windowId: WindowManager.getId(window),
selected: false,
highlighted: false,
active: false,
pinned: false,
incognito: Boolean(tab.state && tab.state.isPrivate),
};
return result;
},
getTabs(window) {
return Array.from(window.gBrowser.tabs)
.filter(tab => !tab.closing)
@ -912,6 +929,19 @@ global.WindowManager = {
return null;
},
getState(window) {
const STATES = {
[window.STATE_MAXIMIZED]: "maximized",
[window.STATE_MINIMIZED]: "minimized",
[window.STATE_NORMAL]: "normal",
};
let state = STATES[window.windowState];
if (window.fullScreen) {
state = "fullscreen";
}
return state;
},
setState(window, state) {
if (state != "fullscreen" && window.fullScreen) {
window.fullScreen = false;
@ -952,16 +982,6 @@ global.WindowManager = {
},
convert(extension, window, getInfo) {
const STATES = {
[window.STATE_MAXIMIZED]: "maximized",
[window.STATE_MINIMIZED]: "minimized",
[window.STATE_NORMAL]: "normal",
};
let state = STATES[window.windowState];
if (window.fullScreen) {
state = "fullscreen";
}
let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
@ -976,7 +996,7 @@ global.WindowManager = {
height: window.outerHeight,
incognito: PrivateBrowsingUtils.isWindowPrivate(window),
type: this.windowType(window),
state,
state: this.getState(window),
alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
};
@ -986,6 +1006,28 @@ global.WindowManager = {
return result;
},
// Converts windows returned from SessionStore.getClosedWindowData
// into API window objects
convertFromSessionStoreClosedData(window, extension) {
let result = {
sessionId: String(window.closedId),
focused: false,
incognito: false,
type: "normal", // this is always "normal" for a closed window
state: this.getState(window),
alwaysOnTop: false,
};
if (window.tabs.length) {
result.tabs = [];
window.tabs.forEach((tab, index) => {
result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
});
}
return result;
},
};
// Manages listeners for window opening and closing. A window is

Просмотреть файл

@ -6,6 +6,7 @@ category webextension-scripts contextMenus chrome://browser/content/ext-contextM
category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
category webextension-scripts history chrome://browser/content/ext-history.js
category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
category webextension-scripts sessions chrome://browser/content/ext-sessions.js
category webextension-scripts tabs chrome://browser/content/ext-tabs.js
category webextension-scripts utils chrome://browser/content/ext-utils.js
category webextension-scripts windows chrome://browser/content/ext-windows.js
@ -24,5 +25,6 @@ category webextension-schemas context_menus chrome://browser/content/schemas/con
category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
category webextension-schemas history chrome://browser/content/schemas/history.json
category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
category webextension-schemas windows chrome://browser/content/schemas/windows.json

Просмотреть файл

@ -19,6 +19,7 @@ browser.jar:
content/browser/ext-desktop-runtime.js
content/browser/ext-history.js
content/browser/ext-pageAction.js
content/browser/ext-sessions.js
content/browser/ext-tabs.js
content/browser/ext-utils.js
content/browser/ext-windows.js

Просмотреть файл

@ -10,5 +10,6 @@ browser.jar:
content/browser/schemas/context_menus_internal.json
content/browser/schemas/history.json
content/browser/schemas/page_action.json
content/browser/schemas/sessions.json
content/browser/schemas/tabs.json
content/browser/schemas/windows.json

Просмотреть файл

@ -0,0 +1,147 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "manifest",
"types": [
{
"$extend": "Permission",
"choices": [{
"type": "string",
"enum": [
"sessions"
]
}]
}
]
},
{
"namespace": "sessions",
"description": "Use the <code>chrome.sessions</code> API to query and restore tabs and windows from a browsing session.",
"permissions": ["sessions"],
"types": [
{
"id": "Filter",
"type": "object",
"properties": {
"maxResults": {
"type": "integer",
"minimum": 0,
"maximum": 25,
"optional": true,
"description": "The maximum number of entries to be fetched in the requested list. Omit this parameter to fetch the maximum number of entries ($(ref:sessions.MAX_SESSION_RESULTS))."
}
}
},
{
"id": "Session",
"type": "object",
"properties": {
"lastModified": {"type": "integer", "description": "The time when the window or tab was closed or modified, represented in milliseconds since the epoch."},
"tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab), if this entry describes a tab. Either this or $(ref:sessions.Session.window) will be set."},
"window": {"$ref": "windows.Window", "optional": true, "description": "The $(ref:windows.Window), if this entry describes a window. Either this or $(ref:sessions.Session.tab) will be set."}
}
},
{
"id": "Device",
"type": "object",
"properties": {
"info": {"type": "string"},
"deviceName": {"type": "string", "description": "The name of the foreign device."},
"sessions": {"type": "array", "items": {"$ref": "Session"}, "description": "A list of open window sessions for the foreign device, sorted from most recently to least recently modified session."}
}
}
],
"functions": [
{
"name": "getRecentlyClosed",
"type": "function",
"description": "Gets the list of recently closed tabs and/or windows.",
"async": "callback",
"parameters": [
{
"$ref": "Filter",
"name": "filter",
"optional": true,
"default": {}
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "sessions", "type": "array", "items": { "$ref": "Session" }, "description": "The list of closed entries in reverse order that they were closed (the most recently closed tab or window will be at index <code>0</code>). The entries may contain either tabs or windows."
}
]
}
]
},
{
"name": "getDevices",
"unsupported": true,
"type": "function",
"description": "Retrieves all devices with synced sessions.",
"async": "callback",
"parameters": [
{
"$ref": "Filter",
"name": "filter",
"optional": true
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "devices", "type": "array", "items": { "$ref": "Device" }, "description": "The list of $(ref:sessions.Device) objects for each synced session, sorted in order from device with most recently modified session to device with least recently modified session. $(ref:tabs.Tab) objects are sorted by recency in the $(ref:windows.Window) of the $(ref:sessions.Session) objects."
}
]
}
]
},
{
"name": "restore",
"unsupported": true,
"type": "function",
"description": "Reopens a $(ref:windows.Window) or $(ref:tabs.Tab), with an optional callback to run when the entry has been restored.",
"async": "callback",
"parameters": [
{
"type": "string",
"name": "sessionId",
"optional": true,
"description": "The $(ref:windows.Window.sessionId), or $(ref:tabs.Tab.sessionId) to restore. If this parameter is not specified, the most recently closed session is restored."
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": [
{
"$ref": "Session",
"name": "restoredSession",
"description": "A $(ref:sessions.Session) containing the restored $(ref:windows.Window) or $(ref:tabs.Tab) object."
}
]
}
]
}
],
"events": [
{
"name": "onChanged",
"unsupported": true,
"description": "Fired when recently closed tabs and/or windows are changed. This event does not monitor synced sessions changes.",
"type": "function"
}
],
"properties": {
"MAX_SESSION_RESULTS": {
"value": 25,
"description": "The maximum number of $(ref:sessions.Session) that will be included in a requested list."
}
}
}
]

Просмотреть файл

@ -78,7 +78,6 @@
"description": "Whether the window is set to be always on top."
},
"sessionId": {
"unsupported": true,
"type": "string",
"optional": true,
"description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."

Просмотреть файл

@ -2,6 +2,7 @@
support-files =
head.js
head_pageAction.js
head_sessions.js
context.html
ctxmenu-image.png
context_tabs_onUpdated_page.html
@ -58,6 +59,8 @@ tags = webextensions
[browser_ext_runtime_openOptionsPage.js]
[browser_ext_runtime_openOptionsPage_uninstall.js]
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_sessions_getRecentlyClosed.js]
[browser_ext_sessions_getRecentlyClosed_private.js]
[browser_ext_simple.js]
[browser_ext_tab_runtimeConnect.js]
[browser_ext_tabs_audio.js]

Просмотреть файл

@ -0,0 +1,97 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
SimpleTest.requestCompleteLog();
Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
this);
add_task(function* test_sessions_get_recently_closed() {
function* openAndCloseWindow(url = "http://example.com", tabUrls) {
let win = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
if (tabUrls) {
for (let url of tabUrls) {
yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
}
}
yield BrowserTestUtils.closeWindow(win);
}
function background() {
Promise.all([
browser.sessions.getRecentlyClosed(),
browser.tabs.query({active: true, currentWindow: true}),
]).then(([recentlyClosed, tabs]) => {
browser.test.sendMessage("initialData", {recentlyClosed, currentWindowId: tabs[0].windowId});
});
browser.test.onMessage.addListener((msg, filter) => {
if (msg == "check-sessions") {
browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
browser.test.sendMessage("recentlyClosed", recentlyClosed);
});
}
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["sessions", "tabs"],
},
background,
});
// Open and close a window that will be ignored, to prove that we are removing previous entries
yield openAndCloseWindow();
yield extension.startup();
let {recentlyClosed, currentWindowId} = yield extension.awaitMessage("initialData");
recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
yield openAndCloseWindow();
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 1, currentWindowId);
yield openAndCloseWindow("about:config", ["about:robots", "about:mozilla"]);
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
// Check for multiple tabs in most recently closed window
is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
yield BrowserTestUtils.removeTab(tab);
tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
yield BrowserTestUtils.removeTab(tab);
yield openAndCloseWindow();
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
let finalResult = recentlyClosed.filter(onlyNewItemsFilter);
checkRecentlyClosed(finalResult, 5, currentWindowId);
isnot(finalResult[0].window, undefined, "first item is a window");
is(finalResult[0].tab, undefined, "first item is not a tab");
isnot(finalResult[1].tab, undefined, "second item is a tab");
is(finalResult[1].window, undefined, "second item is not a window");
isnot(finalResult[2].tab, undefined, "third item is a tab");
is(finalResult[2].window, undefined, "third item is not a window");
isnot(finalResult[3].window, undefined, "fourth item is a window");
is(finalResult[3].tab, undefined, "fourth item is not a tab");
isnot(finalResult[4].window, undefined, "fifth item is a window");
is(finalResult[4].tab, undefined, "fifth item is not a tab");
// test with filter
extension.sendMessage("check-sessions", {maxResults: 2});
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, currentWindowId);
yield extension.unload();
});

Просмотреть файл

@ -0,0 +1,61 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
SimpleTest.requestCompleteLog();
Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
this);
add_task(function* test_sessions_get_recently_closed_private() {
function background() {
browser.test.onMessage.addListener((msg, filter) => {
if (msg == "check-sessions") {
browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
browser.test.sendMessage("recentlyClosed", recentlyClosed);
});
}
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["sessions", "tabs"],
},
background,
});
// Open a private browsing window.
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
yield extension.startup();
let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let privateWinId = WindowManager.getId(privateWin);
extension.sendMessage("check-sessions");
let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
// Open and close two tabs in the private window
let tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
yield BrowserTestUtils.removeTab(tab);
tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
yield BrowserTestUtils.removeTab(tab);
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, privateWinId, true);
// Close the private window.
yield BrowserTestUtils.closeWindow(privateWin);
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
is(recentlyClosed.filter(onlyNewItemsFilter).length, 0, "the closed private window info was not found in recently closed data");
yield extension.unload();
});

Просмотреть файл

@ -0,0 +1,47 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* exported recordInitialTimestamps onlyNewItemsFilter checkRecentlyClosed */
let initialTimestamps = [];
function recordInitialTimestamps(timestamps) {
initialTimestamps = timestamps;
}
function onlyNewItemsFilter(item) {
return !initialTimestamps.includes(item.lastModified);
}
function checkWindow(window) {
for (let prop of ["focused", "incognito", "alwaysOnTop"]) {
is(window[prop], false, `closed window has the expected value for ${prop}`);
}
for (let prop of ["state", "type"]) {
is(window[prop], "normal", `closed window has the expected value for ${prop}`);
}
}
function checkTab(tab, windowId, incognito) {
for (let prop of ["selected", "highlighted", "active", "pinned"]) {
is(tab[prop], false, `closed tab has the expected value for ${prop}`);
}
is(tab.windowId, windowId, "closed tab has the expected value for windowId");
is(tab.incognito, incognito, "closed tab has the expected value for incognito");
}
function checkRecentlyClosed(recentlyClosed, expectedCount, windowId, incognito = false) {
let sessionIds = new Set();
is(recentlyClosed.length, expectedCount, "the expected number of closed tabs/windows was found");
for (let item of recentlyClosed) {
if (item.window) {
sessionIds.add(item.window.sessionId);
checkWindow(item.window);
} else if (item.tab) {
sessionIds.add(item.tab.sessionId);
checkTab(item.tab, windowId, incognito);
}
}
is(sessionIds.size, expectedCount, "each item has a unique sessionId");
}

Просмотреть файл

@ -1022,6 +1022,9 @@ void HTMLMediaElement::AbortExistingLoads()
void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
{
if (mDecoder) {
ShutdownDecoder();
}
mError = new MediaError(this, MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
DispatchAsyncEvent(NS_LITERAL_STRING("error"));
@ -4413,9 +4416,6 @@ void HTMLMediaElement::FirstFrameLoaded()
void HTMLMediaElement::NetworkError()
{
if (mDecoder) {
ShutdownDecoder();
}
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
NoSupportedMediaSourceError();
} else {
@ -4430,9 +4430,6 @@ void HTMLMediaElement::DecodeError(const MediaResult& aError)
const char16_t* params[] = { src.get() };
ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
if (mDecoder) {
ShutdownDecoder();
}
AudioTracks()->EmptyTracks();
VideoTracks()->EmptyTracks();
if (mIsLoadingFromSourceChildren) {

Просмотреть файл

@ -896,7 +896,6 @@ MediaDecoder::NetworkError()
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdown());
mOwner->NetworkError();
MOZ_ASSERT(IsShutdown());
}
void
@ -905,7 +904,6 @@ MediaDecoder::DecodeError(const MediaResult& aError)
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdown());
mOwner->DecodeError(aError);
MOZ_ASSERT(IsShutdown());
}
void

Просмотреть файл

@ -89,8 +89,7 @@ MP4Decoder::CanHandleMediaType(const MediaContentType& aType,
// the web, as opposed to what we use internally (i.e. what our demuxers
// etc output).
const bool isMP4Audio = aType.GetMIMEType().EqualsASCII("audio/mp4") ||
aType.GetMIMEType().EqualsASCII("audio/x-m4a") ||
aType.GetMIMEType().EqualsASCII("audio/opus");
aType.GetMIMEType().EqualsASCII("audio/x-m4a");
const bool isMP4Video =
// On B2G, treat 3GPP as MP4 when Gonk PDM is available.
#ifdef MOZ_GONK_MEDIACODEC
@ -105,7 +104,7 @@ MP4Decoder::CanHandleMediaType(const MediaContentType& aType,
nsTArray<UniquePtr<TrackInfo>> trackInfos;
if (aType.GetCodecs().IsEmpty()) {
// No codecs specified. Assume AAC/H.264
// No codecs specified. Assume H.264
if (isMP4Audio) {
trackInfos.AppendElement(
CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
@ -136,6 +135,18 @@ MP4Decoder::CanHandleMediaType(const MediaContentType& aType,
NS_LITERAL_CSTRING("audio/mpeg"), aType));
continue;
}
if (codec.EqualsLiteral("opus")) {
trackInfos.AppendElement(
CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
NS_LITERAL_CSTRING("audio/opus"), aType));
continue;
}
if (codec.EqualsLiteral("flac")) {
trackInfos.AppendElement(
CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
NS_LITERAL_CSTRING("audio/flac"), aType));
continue;
}
// Note: Only accept H.264 in a video content type, not in an audio
// content type.
if (IsWhitelistedH264Codec(codec) && isMP4Video) {

Просмотреть файл

@ -729,10 +729,11 @@ DecodedStream::NotifyOutput(int64_t aTime)
{
AssertOwnerThread();
mLastOutputTime = aTime;
int64_t currentTime = GetPosition();
// Remove audio samples that have been played by MSG from the queue.
RefPtr<MediaData> a = mAudioQueue.PeekFront();
for (; a && a->mTime < aTime;) {
for (; a && a->mTime < currentTime;) {
RefPtr<MediaData> releaseMe = mAudioQueue.PopFront();
a = mAudioQueue.PeekFront();
}

Просмотреть файл

@ -38,7 +38,7 @@ ContainerParser::ContainerParser(const nsACString& aType)
ContainerParser::~ContainerParser() = default;
bool
MediaResult
ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData)
{
MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
@ -47,10 +47,10 @@ ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData)
aData->Length() > 1 ? (*aData)[1] : 0,
aData->Length() > 2 ? (*aData)[2] : 0,
aData->Length() > 3 ? (*aData)[3] : 0);
return false;
return NS_ERROR_NOT_AVAILABLE;
}
bool
MediaResult
ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData)
{
MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
@ -59,14 +59,14 @@ ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData)
aData->Length() > 1 ? (*aData)[1] : 0,
aData->Length() > 2 ? (*aData)[2] : 0,
aData->Length() > 3 ? (*aData)[3] : 0);
return false;
return NS_ERROR_NOT_AVAILABLE;
}
bool
MediaResult
ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd)
{
return false;
return NS_ERROR_NOT_AVAILABLE;
}
bool
@ -123,7 +123,7 @@ public:
static const unsigned NS_PER_USEC = 1000;
static const unsigned USEC_PER_SEC = 1000000;
bool IsInitSegmentPresent(MediaByteBuffer* aData) override
MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
{
ContainerParser::IsInitSegmentPresent(aData);
// XXX: This is overly primitive, needs to collect data as it's appended
@ -140,15 +140,17 @@ public:
// 0x1654ae6b // -> One or more Tracks
// 0x1a45dfa3 // EBML
if (aData->Length() >= 4 &&
(*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
(*aData)[3] == 0xa3) {
return true;
if (aData->Length() < 4) {
return NS_ERROR_NOT_AVAILABLE;
}
return false;
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"));
}
bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
ContainerParser::IsMediaSegmentPresent(aData);
// XXX: This is overly primitive, needs to collect data as it's appended
@ -163,26 +165,29 @@ public:
// 0x1654ae6b // -> One or more Tracks
// 0x1f43b675 // Cluster
if (aData->Length() >= 4 &&
(*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
if (aData->Length() < 4) {
return NS_ERROR_NOT_AVAILABLE;
}
if ((*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
(*aData)[3] == 0x75) {
return true;
return NS_OK;
}
// 0x1c53bb6b // Cues
if (aData->Length() >= 4 &&
(*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
if ((*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
(*aData)[3] == 0x6b) {
return true;
return NS_OK;
}
return false;
return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
}
bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd) override
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
bool initSegment = IsInitSegmentPresent(aData);
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (mLastMapping && (initSegment || IsMediaSegmentPresent(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
@ -191,7 +196,7 @@ public:
mOffset);
mLastMapping.reset();
MSE_DEBUG(WebMContainerParser, "New cluster found at start, ending previous one");
return false;
return NS_ERROR_NOT_AVAILABLE;
}
if (initSegment) {
@ -223,7 +228,7 @@ public:
MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) {
// Super unlikely OOM
return false;
return NS_ERROR_OUT_OF_MEMORY;
}
mCompleteInitSegmentRange = MediaByteRange(0, mParser.mInitEndOffset);
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
@ -239,7 +244,7 @@ public:
mOffset += aData->Length();
if (mapping.IsEmpty()) {
return false;
return NS_ERROR_NOT_AVAILABLE;
}
// Calculate media range for first media segment.
@ -265,7 +270,7 @@ public:
if (completeIdx < 0) {
mLastMapping.reset();
return false;
return NS_ERROR_NOT_AVAILABLE;
}
if (mCompleteMediaHeaderRange.IsEmpty()) {
@ -300,7 +305,7 @@ public:
if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
// We have no previous nor next block available,
// so we can't estimate this block's duration.
return false;
return NS_ERROR_NOT_AVAILABLE;
}
uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
@ -314,7 +319,7 @@ public:
mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
mCompleteMediaSegmentRange.mEnd);
return true;
return NS_OK;
}
int64_t GetRoundingError() override
@ -337,20 +342,30 @@ public:
: ContainerParser(aType)
{}
bool IsInitSegmentPresent(MediaByteBuffer* aData) override
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.
AtomParser parser(mType, aData);
return parser.StartWithInitSegment();
if (!parser.IsValid()) {
return MediaResult(
NS_ERROR_FAILURE,
RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
}
return parser.StartWithInitSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
AtomParser parser(mType, aData);
return parser.StartWithMediaSegment();
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:
@ -363,13 +378,38 @@ private:
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.
};
while (reader.Remaining() >= 8) {
uint64_t size = reader.ReadU32();
const uint8_t* typec = reader.Peek(4);
uint32_t type = reader.ReadU32();
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);
for (const auto& boxType : validBoxes) {
if (type == boxType) {
mValid = true;
break;
}
}
if (!mValid) {
// No point continuing.
mLastInvalidBox[0] = typec[3];
mLastInvalidBox[1] = typec[2];
mLastInvalidBox[2] = typec[1];
mLastInvalidBox[3] = typec[0];
mLastInvalidBox[4] = '\0';
break;
}
if (mInitOffset.isNothing() &&
mp4_demuxer::AtomType(type) == initAtom) {
mInitOffset = Some(reader.Offset());
@ -401,26 +441,31 @@ private:
}
}
bool StartWithInitSegment()
bool StartWithInitSegment() const
{
return mInitOffset.isSome() &&
(mMediaOffset.isNothing() || mInitOffset.ref() < mMediaOffset.ref());
}
bool StartWithMediaSegment()
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 = false;
char mLastInvalidBox[5];
};
public:
bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd) override
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
bool initSegment = IsInitSegmentPresent(aData);
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (initSegment) {
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
mStream = new MP4Stream(mResource);
@ -431,7 +476,7 @@ public:
mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false);
mInitData = new MediaByteBuffer();
} else if (!mStream || !mParser) {
return false;
return NS_ERROR_NOT_AVAILABLE;
}
mResource->AppendData(aData);
@ -446,7 +491,7 @@ public:
mCompleteInitSegmentRange = range;
if (!mInitData->SetLength(range.Length(), fallible)) {
// Super unlikely OOM
return false;
return NS_ERROR_OUT_OF_MEMORY;
}
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
mResource->ReadFromCache(buffer, range.mStart, range.Length());
@ -469,17 +514,17 @@ public:
}
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
return NS_ERROR_OUT_OF_MEMORY;
}
if (compositionRange.IsNull()) {
return false;
return NS_ERROR_NOT_AVAILABLE;
}
aStart = compositionRange.start;
aEnd = compositionRange.end;
MSE_DEBUG(MP4ContainerParser, "[%lld, %lld]",
aStart, aEnd);
return true;
return NS_OK;
}
// Gaps of up to 35ms (marginally longer than a single frame at 30fps) are considered
@ -554,24 +599,24 @@ public:
return true;
}
bool IsInitSegmentPresent(MediaByteBuffer* aData) override
MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
{
// Call superclass for logging.
ContainerParser::IsInitSegmentPresent(aData);
Header header;
if (!Parse(aData, header)) {
return false;
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 true;
return NS_OK;
}
bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
{
// Call superclass for logging.
ContainerParser::IsMediaSegmentPresent(aData);
@ -583,26 +628,27 @@ public:
// mInitData if we need to handle separate media segments.
Header header;
if (!Parse(aData, header)) {
return false;
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 false;
return NS_ERROR_NOT_AVAILABLE;
}
// We should have at least a partial frame.
return true;
return NS_OK;
}
bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd) override
MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart,
int64_t& aEnd) override
{
// ADTS header.
Header header;
if (!Parse(aData, header)) {
return false;
return NS_ERROR_NOT_AVAILABLE;
}
mHasInitData = true;
mCompleteInitSegmentRange = MediaByteRange(0, int64_t(header.header_length));
@ -617,7 +663,7 @@ public:
" in %llu byte buffer.",
(unsigned long long)header.frame_length,
(unsigned long long)(aData->Length()));
return false;
return NS_ERROR_NOT_AVAILABLE;
}
mCompleteMediaSegmentRange = MediaByteRange(header.header_length,
header.frame_length);
@ -629,7 +675,7 @@ public:
MSE_DEBUG(ADTSContainerParser, "[%lld, %lld]",
aStart, aEnd);
// We don't update timestamps, regardless.
return false;
return NS_ERROR_NOT_AVAILABLE;
}
// Audio shouldn't have gaps.

Просмотреть файл

@ -10,6 +10,7 @@
#include "mozilla/RefPtr.h"
#include "nsString.h"
#include "MediaResource.h"
#include "MediaResult.h"
namespace mozilla {
@ -24,18 +25,25 @@ public:
// 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.
virtual bool IsInitSegmentPresent(MediaByteBuffer* aData);
// 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.
virtual bool IsMediaSegmentPresent(MediaByteBuffer* aData);
// 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 true
// if aStart and aEnd have been updated.
virtual bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd);
// 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

Просмотреть файл

@ -636,7 +636,8 @@ TrackBuffersManager::SegmentParserLoop()
// 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
// steps:
if (mSourceBufferAttributes->GetAppendState() == AppendState::WAITING_FOR_SEGMENT) {
if (mParser->IsInitSegmentPresent(mInputBuffer)) {
MediaResult haveInitSegment = mParser->IsInitSegmentPresent(mInputBuffer);
if (NS_SUCCEEDED(haveInitSegment)) {
SetAppendState(AppendState::PARSING_INIT_SEGMENT);
if (mFirstInitializationSegmentReceived) {
// This is a new initialization segment. Obsolete the old one.
@ -644,20 +645,37 @@ TrackBuffersManager::SegmentParserLoop()
}
continue;
}
if (mParser->IsMediaSegmentPresent(mInputBuffer)) {
MediaResult haveMediaSegment =
mParser->IsMediaSegmentPresent(mInputBuffer);
if (NS_SUCCEEDED(haveMediaSegment)) {
SetAppendState(AppendState::PARSING_MEDIA_SEGMENT);
mNewMediaSegmentStarted = true;
continue;
}
// We have neither an init segment nor a media segment, this is either
// invalid data or not enough data to detect a segment type.
MSE_DEBUG("Found invalid or incomplete data.");
// We have neither an init segment nor a media segment.
// Check if it was invalid data.
if (haveInitSegment != NS_ERROR_NOT_AVAILABLE) {
MSE_DEBUG("Found invalid data.");
RejectAppend(haveInitSegment, __func__);
return;
}
if (haveMediaSegment != NS_ERROR_NOT_AVAILABLE) {
MSE_DEBUG("Found invalid data.");
RejectAppend(haveMediaSegment, __func__);
return;
}
MSE_DEBUG("Found incomplete data.");
NeedMoreData();
return;
}
int64_t start, end;
bool newData = mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
MediaResult newData =
mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
if (!NS_SUCCEEDED(newData) && newData.Code() != NS_ERROR_NOT_AVAILABLE) {
RejectAppend(newData, __func__);
return;
}
mProcessedInput += mInputBuffer->Length();
// 5. If the append state equals PARSING_INIT_SEGMENT, then run the
@ -684,13 +702,13 @@ TrackBuffersManager::SegmentParserLoop()
// If so, recreate a new demuxer to ensure that the demuxer is only fed
// monotonically increasing data.
if (mNewMediaSegmentStarted) {
if (newData && mLastParsedEndTime.isSome() &&
if (NS_SUCCEEDED(newData) && mLastParsedEndTime.isSome() &&
start < mLastParsedEndTime.ref().ToMicroseconds()) {
MSE_DEBUG("Re-creating demuxer");
ResetDemuxingState();
return;
}
if (newData || !mParser->MediaSegmentRange().IsEmpty()) {
if (NS_SUCCEEDED(newData) || !mParser->MediaSegmentRange().IsEmpty()) {
if (mPendingInputBuffer) {
// We now have a complete media segment header. We can resume parsing
// the data.

Просмотреть файл

@ -47,37 +47,37 @@ TEST(ContainerParser, ADTSHeader) {
// Test a valid header.
RefPtr<MediaByteBuffer> header = make_adts_header();
EXPECT_TRUE(parser->IsInitSegmentPresent(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(parser->IsInitSegmentPresent(header))
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(parser->IsInitSegmentPresent(header))
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(parser->IsInitSegmentPresent(header))
EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
<< "Accepted too-short header.";
EXPECT_FALSE(parser->IsMediaSegmentPresent(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(parser->IsMediaSegmentPresent(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_FALSE(parser->ParseStartAndEndTimestamps(header, start, end));
EXPECT_TRUE(NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
EXPECT_TRUE(parser->HasInitData());
EXPECT_TRUE(parser->HasCompleteInitData());

Просмотреть файл

@ -224,8 +224,6 @@ H264Converter::CreateDecoderAndInit(MediaRawData* aSample)
// Queue the incoming sample.
mMediaRawSamples.AppendElement(aSample);
RefPtr<H264Converter> self = this;
mInitPromiseRequest.Begin(mDecoder->Init()
->Then(AbstractThread::GetCurrent()->AsTaskQueue(), __func__, this,
&H264Converter::OnDecoderInitDone,

Просмотреть файл

@ -78,6 +78,16 @@ function check_mp4(v, enabled) {
check("audio/x-m4a; codecs=mp4a.40.5", "probably");
// HE-AAC v2
check("audio/mp4; codecs=\"mp4a.40.29\"", "probably");
// Opus
check("audio/mp4; codecs=\"opus\"", "probably");
check("audio/mp4; codecs=opus", "probably");
// Flac.
// Not available on Android yet.
var expectedResult = IsSupportedAndroid() ? "" : "probably";
check("audio/mp4; codecs=\"flac\"", expectedResult);
check("audio/mp4; codecs=flac", expectedResult);
}
function check_mp3(v, enabled) {

Просмотреть файл

@ -33,7 +33,7 @@ VIntLength(unsigned char aFirstByte, uint32_t* aMask)
return count;
}
void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
bool WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
nsTArray<WebMTimeDataOffset>& aMapping,
ReentrantMonitor& aReentrantMonitor)
{
@ -163,7 +163,9 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
}
break;
case READ_TIMECODESCALE:
MOZ_ASSERT(mGotTimecodeScale);
if (!mGotTimecodeScale) {
return false;
}
mTimecodeScale = mVInt.mValue;
mState = READ_ELEMENT_ID;
break;
@ -187,7 +189,9 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
if (idx == 0 || aMapping[idx - 1] != endOffset) {
// Don't insert invalid negative timecodes.
if (mBlockTimecode >= 0 || mClusterTimecode >= uint16_t(abs(mBlockTimecode))) {
MOZ_ASSERT(mGotTimecodeScale);
if (!mGotTimecodeScale) {
return false;
}
uint64_t absTimecode = mClusterTimecode + mBlockTimecode;
absTimecode *= mTimecodeScale;
// Avoid creating an entry if the timecode is out of order
@ -248,6 +252,8 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
mCurrentOffset += aLength;
return true;
}
int64_t

Просмотреть файл

@ -96,7 +96,8 @@ struct WebMBufferedParser
// Steps the parser through aLength bytes of data. Always consumes
// aLength bytes. Updates mCurrentOffset before returning. Acquires
// aReentrantMonitor before using aMapping.
void Append(const unsigned char* aBuffer, uint32_t aLength,
// Returns false if an error was encountered.
bool Append(const unsigned char* aBuffer, uint32_t aLength,
nsTArray<WebMTimeDataOffset>& aMapping,
ReentrantMonitor& aReentrantMonitor);

Просмотреть файл

@ -1,10 +1,10 @@
[clearkey-mp4-playback-temporary-encrypted-clear.html]
type: testharness
expected:
if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
[org.w3.clearkey, temporary, mp4, playback, single key, encrypted then clear content]
expected:
if not debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT

Просмотреть файл

@ -1,18 +1,8 @@
[clearkey-mp4-playback-temporary-multisession.html]
type: testharness
expected:
if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
[org.w3.clearkey, temporary, mp4, playback with multiple sessions, multikey video]
expected:
if not debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT

Просмотреть файл

@ -1,14 +0,0 @@
[mediasource-errors.html]
type: testharness
expected:
if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): CRASH
if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
[Signaling 'decode' error via endOfStream() after initialization segment has been appended and the HTMLMediaElement has reached HAVE_METADATA.]
expected: FAIL
[Signaling 'network' error via endOfStream() after initialization segment has been appended and the HTMLMediaElement has reached HAVE_METADATA.]
expected: FAIL
[Signaling 'decode' error via segment parser loop algorithm after initialization segment and partial media segment has been appended.]
expected: FAIL

Просмотреть файл

@ -174,13 +174,49 @@
{
assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
var initSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.init);
test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
test.expectEvent(mediaElement, "loadedmetadata", "mediaElement metadata.");
sourceBuffer.appendBuffer(initSegment);
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_METADATA);
var mediaSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[0]);
var index = segmentInfo.init.size + (mediaSegment.length - 1) / 2;
// Corrupt the media data from index of mediaData, so it can signal 'decode' error.
// Here use mediaSegment to replace the original mediaData[index, index + mediaSegment.length]
mediaData.set(mediaSegment, index);
test.expectEvent(sourceBuffer, "error", "sourceBuffer error.");
test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
test.expectEvent(mediaElement, "error", "mediaElement error.");
test.expectEvent(mediaSource, "sourceended", "mediaSource ended.");
sourceBuffer.appendBuffer(mediaData);
});
test.waitForExpectedEvents(function()
{
assert_true(mediaElement.error != null);
assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_DECODE);
test.done();
});
}, "Signaling 'decode' error via segment parser loop algorithm after initialization segment has been appended.");
ErrorTest(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
// Fail if the append error algorithm occurs, since the network
// error will be provided by us directly via endOfStream().
mediaElement.addEventListener("loadedmetadata", test.unreached_func("'loadedmetadata' should not be fired on mediaElement"));
var mediaSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[0]);
var index = segmentInfo.init.size + (mediaSegment.length - 1) / 2;
// Corrupt the media data from index of mediaData, so it can signal 'decode' error.
// Here use mediaSegment to replace the original mediaData[index, index + mediaSegment.length]
mediaData.set(mediaSegment, index);
test.expectEvent(mediaElement, "loadedmetadata", "mediaElement metadata.");
test.expectEvent(sourceBuffer, "error", "sourceBuffer error.");
test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
test.expectEvent(mediaElement, "error", "mediaElement error.");
@ -189,9 +225,10 @@
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
assert_true(mediaElement.error != null);
assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_DECODE);
assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
test.done();
});
}, "Signaling 'decode' error via segment parser loop algorithm after initialization segment and partial media segment has been appended.");
}, "Signaling 'decode' error via segment parser loop algorithm.");
</script>