зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1048688
- videos without an extension in the source URL aren't castable r=wesj
This commit is contained in:
Родитель
f977c02132
Коммит
23050006c4
|
@ -83,18 +83,24 @@ function execute_video_test(test) {
|
|||
let element = browser.contentDocument.getElementById(test.id);
|
||||
if (element) {
|
||||
let [x, y] = middle(element);
|
||||
let video = chromeWin.CastingApps.getVideo(element, x, y);
|
||||
if (video) {
|
||||
let matchPoster = (test.poster == video.poster);
|
||||
let matchSource = (test.source == video.source);
|
||||
ok(matchPoster && matchSource && test.pass, test.text);
|
||||
} else {
|
||||
ok(!test.pass, test.text);
|
||||
}
|
||||
do_test_pending();
|
||||
do_print("Starting to getVideo");
|
||||
chromeWin.CastingApps.getVideo(element, x, y, (video) => {
|
||||
do_print("got a Video");
|
||||
if (video) {
|
||||
let matchPoster = (test.poster == video.poster);
|
||||
let matchSource = (test.source == video.source);
|
||||
ok(matchPoster && matchSource && test.pass, test.text);
|
||||
} else {
|
||||
ok(!test.pass, test.text);
|
||||
}
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
});
|
||||
} else {
|
||||
ok(false, "test element not found: [" + test.id + "]");
|
||||
run_next_test();
|
||||
}
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
let videoTest;
|
||||
|
|
|
@ -246,12 +246,12 @@ var CastingApps = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.getVideo(video, 0, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let the binding know casting is allowed
|
||||
this._sendEventToVideo(video, { allow: true });
|
||||
this.getVideo(video, 0, 0, (aBundle) => {
|
||||
// Let the binding know casting is allowed
|
||||
if (aBundle) {
|
||||
this._sendEventToVideo(aBundle.element, { allow: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
|
||||
|
@ -274,43 +274,77 @@ var CastingApps = {
|
|||
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
||||
},
|
||||
|
||||
getVideo: function(aElement, aX, aY) {
|
||||
allowableExtension: function(aURI, aExtensions) {
|
||||
return (aURI instanceof Ci.nsIURL) && aExtensions.indexOf(aURI.fileExtension) != -1;
|
||||
},
|
||||
|
||||
allowableMimeType: function(aType, aTypes) {
|
||||
return aTypes.indexOf(aType) != -1;
|
||||
},
|
||||
|
||||
// This method will look at the aElement (or try to find a video at aX, aY) that has
|
||||
// a castable source. If found, aCallback will be called with a JSON meta bundle. If
|
||||
// no castable source was found, aCallback is called with null.
|
||||
getVideo: function(aElement, aX, aY, aCallback) {
|
||||
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
||||
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
||||
|
||||
// Fast path: Is the given element a video element
|
||||
let video = this._getVideo(aElement, types, extensions);
|
||||
if (video) {
|
||||
return video;
|
||||
// Fast path: Is the given element a video element?
|
||||
if (aElement instanceof HTMLVideoElement) {
|
||||
// If we found a video element, no need to look further, even if no
|
||||
// castable video source is found.
|
||||
this._getVideo(aElement, types, extensions, aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Maybe this is an overlay, with the video element under it.
|
||||
// Use the (x, y) location to guess at a <video> element.
|
||||
|
||||
// The context menu system will keep walking up the DOM giving us a chance
|
||||
// to find an element we match. When it hits <html> things can go BOOM.
|
||||
try {
|
||||
// Maybe this is an overlay, with the video element under it
|
||||
// Use the (x, y) location to guess at a <video> element
|
||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||
for (let element of elements) {
|
||||
// Look for a video element contained in the overlay bounds
|
||||
let rect = element.getBoundingClientRect();
|
||||
if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
|
||||
video = this._getVideo(element, types, extensions);
|
||||
if (video) {
|
||||
break;
|
||||
}
|
||||
// Once we find a <video> under the overlay, we check it and exit.
|
||||
this._getVideo(element, types, extensions, aCallback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// Could be null
|
||||
return video;
|
||||
},
|
||||
|
||||
_getVideo: function(aElement, aTypes, aExtensions) {
|
||||
if (!(aElement instanceof HTMLVideoElement)) {
|
||||
return null;
|
||||
}
|
||||
_getContentTypeForURI: function(aURI, aCallback) {
|
||||
let channel = Services.io.newChannelFromURI(aURI);
|
||||
let listener = {
|
||||
onStartRequest: function(request, context) {
|
||||
switch (channel.responseStatus) {
|
||||
case 301:
|
||||
case 302:
|
||||
case 303:
|
||||
request.cancel(0);
|
||||
let location = channel.getResponseHeader("Location");
|
||||
CastingApps._getContentTypeForURI(CastingApps.makeURI(location), aCallback);
|
||||
break;
|
||||
default:
|
||||
aCallback(channel.contentType);
|
||||
request.cancel(0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
onStopRequest: function(request, context, statusCode) {},
|
||||
onDataAvailable: function(request, context, stream, offset, count) {}
|
||||
};
|
||||
channel.asyncOpen(listener, null)
|
||||
},
|
||||
|
||||
// Because this method uses a callback, make sure we return ASAP if we know
|
||||
// we have a castable video source.
|
||||
_getVideo: function(aElement, aTypes, aExtensions, aCallback) {
|
||||
// Keep a list of URIs we need for an async mimetype check
|
||||
let asyncURIs = [];
|
||||
|
||||
// Grab the poster attribute from the <video>
|
||||
let posterURL = aElement.poster;
|
||||
|
@ -327,8 +361,20 @@ var CastingApps = {
|
|||
// Use the file extension to guess the mime type
|
||||
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI));
|
||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
|
||||
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI});
|
||||
return;
|
||||
}
|
||||
|
||||
if (aElement.type) {
|
||||
// Fast sync check
|
||||
if (this.allowableMimeType(aElement.type, aTypes)) {
|
||||
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aElement.type });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delay the async check until we sync scan all possible URIs
|
||||
asyncURIs.push(sourceURI);
|
||||
}
|
||||
|
||||
// Next, look to see if there is a <source> child element that meets
|
||||
|
@ -339,23 +385,77 @@ var CastingApps = {
|
|||
|
||||
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
||||
// fallback to using the file extension to guess the mime type
|
||||
if (this.allowableMimeType(sourceNode.type, aTypes) || this.allowableExtension(sourceURI, aExtensions)) {
|
||||
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
|
||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceNode.type) {
|
||||
// Fast sync check
|
||||
if (this.allowableMimeType(sourceNode.type, aTypes)) {
|
||||
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delay the async check until we sync scan all possible URIs
|
||||
asyncURIs.push(sourceURI);
|
||||
}
|
||||
|
||||
return null;
|
||||
// If we didn't find a good URI directly, let's look using async methods
|
||||
// As soon as we find a good sourceURL, avoid firing the callback any further
|
||||
aCallback.fired = false;
|
||||
for (let sourceURI of asyncURIs) {
|
||||
// Do an async fetch to figure out the mimetype of the source video
|
||||
this._getContentTypeForURI(sourceURI, (aType) => {
|
||||
if (!aCallback.fired && this.allowableMimeType(aType, aTypes)) {
|
||||
aCallback.fired = true;
|
||||
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aType });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If we didn't find any castable source, let's send back a signal
|
||||
if (!aCallback.fired) {
|
||||
aCallback(null);
|
||||
}
|
||||
},
|
||||
|
||||
// This code depends on handleVideoBindingAttached setting mozAllowCasting
|
||||
// so we can quickly figure out if the video is castable
|
||||
isVideoCastable: function(aElement, aX, aY) {
|
||||
// Use the flag set when the <video> binding was created as the check
|
||||
if (aElement instanceof HTMLVideoElement) {
|
||||
return aElement.mozAllowCasting;
|
||||
}
|
||||
|
||||
// This is called by the context menu system and the system will keep
|
||||
// walking up the DOM giving us a chance to find an element we match.
|
||||
// When it hits <html> things can go BOOM.
|
||||
try {
|
||||
// Maybe this is an overlay, with the video element under it
|
||||
// Use the (x, y) location to guess at a <video> element
|
||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||
for (let element of elements) {
|
||||
// Look for a video element contained in the overlay bounds
|
||||
let rect = element.getBoundingClientRect();
|
||||
if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
|
||||
// Use the flag set when the <video> binding was created as the check
|
||||
return element.mozAllowCasting;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
filterCast: {
|
||||
matches: function(aElement, aX, aY) {
|
||||
// This behavior matches the pageaction: As long as a video is castable,
|
||||
// we can cast it, even if it's already being cast to a device.
|
||||
if (SimpleServiceDiscovery.services.length == 0)
|
||||
return false;
|
||||
let video = CastingApps.getVideo(aElement, aX, aY);
|
||||
if (CastingApps.session) {
|
||||
return (video && CastingApps.session.data.source != video.source);
|
||||
}
|
||||
return (video != null);
|
||||
return CastingApps.isVideoCastable(aElement, aX, aY);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -495,13 +595,16 @@ var CastingApps = {
|
|||
|
||||
openExternal: function(aElement, aX, aY) {
|
||||
// Start a second screen media service
|
||||
let video = this.getVideo(aElement, aX, aY);
|
||||
if (!video) {
|
||||
this.getVideo(aElement, aX, aY, this._openExternal.bind(this));
|
||||
},
|
||||
|
||||
_openExternal: function(aVideo) {
|
||||
if (!aVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
function filterFunc(service) {
|
||||
return this.allowableExtension(video.sourceURI, service.extensions) || this.allowableMimeType(video.type, service.types);
|
||||
function filterFunc(aService) {
|
||||
return this.allowableExtension(aVideo.sourceURI, aService.extensions) || this.allowableMimeType(aVideo.type, aService.types);
|
||||
}
|
||||
|
||||
this.prompt(function(aService) {
|
||||
|
@ -513,11 +616,12 @@ var CastingApps = {
|
|||
if (!app)
|
||||
return;
|
||||
|
||||
video.title = aElement.ownerDocument.defaultView.top.document.title;
|
||||
if (video.element) {
|
||||
if (aVideo.element) {
|
||||
aVideo.title = aVideo.element.ownerDocument.defaultView.top.document.title;
|
||||
|
||||
// If the video is currently playing on the device, pause it
|
||||
if (!video.element.paused) {
|
||||
video.element.pause();
|
||||
if (!aVideo.element.paused) {
|
||||
aVideo.element.pause();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,11 +643,11 @@ var CastingApps = {
|
|||
app: app,
|
||||
remoteMedia: aRemoteMedia,
|
||||
data: {
|
||||
title: video.title,
|
||||
source: video.source,
|
||||
poster: video.poster
|
||||
title: aVideo.title,
|
||||
source: aVideo.source,
|
||||
poster: aVideo.poster
|
||||
},
|
||||
videoRef: Cu.getWeakReference(video.element)
|
||||
videoRef: Cu.getWeakReference(aVideo.element)
|
||||
};
|
||||
}.bind(this), this);
|
||||
}.bind(this));
|
||||
|
@ -601,21 +705,5 @@ var CastingApps = {
|
|||
if (status == "completed") {
|
||||
this.closeExternal();
|
||||
}
|
||||
},
|
||||
|
||||
allowableExtension: function(aURI, aExtensions) {
|
||||
if (aURI && aURI instanceof Ci.nsIURL) {
|
||||
for (let x in aExtensions) {
|
||||
if (aURI.fileExtension == aExtensions[x]) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
allowableMimeType: function(aType, aTypes) {
|
||||
for (let x in aTypes) {
|
||||
if (aType == aTypes[x]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче