Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Big refactoring to include fallback in new browsers that

don't support the Flash codecs (Firefox, 3.1+, Opera 100.5+).

This is a pretty big milestone that essentially
completes <audio> support, except for minor technicalities
which will be ironed out in the near future.

<video> support has also begun, however there is still a big
technical hurdle to overcome: making the Flash object mimic
the <video> element's style and placement in the DOM. Also
detecting WHEN the <video> element is inserted/removed/replaced
in the DOM.
  • Loading branch information...
commit 5a6742ab51991972837180e75f6197ce26760f18 1 parent e5c5417
@TooTallNate authored
View
14 HTMLMediaElement.htc → HtmlAudio.htc
@@ -20,7 +20,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-<PUBLIC:COMPONENT NAME="HTMLMediaElement" LIGHTWEIGHT=true>
+<PUBLIC:COMPONENT NAME="HTMLAudioElement" LIGHTWEIGHT=true>
<PUBLIC:ATTACH EVENT="oncontentready" ONEVENT="__initialize()"/>
<PUBLIC:PROPERTY NAME="error" GET="errorGet" />
@@ -36,6 +36,9 @@
<PUBLIC:PROPERTY NAME="controls" GET="controlsGet" PUT="controlsSet" />
<PUBLIC:PROPERTY NAME="volume" GET="volumeGet" PUT="volumeSet" />
<PUBLIC:PROPERTY NAME="muted" GET="mutedGet" PUT="mutedSet" />
+ <PUBLIC:PROPERTY NAME="buffered" GET="bufferedGet" />
+ <PUBLIC:PROPERTY NAME="played" GET="playedGet" />
+ <PUBLIC:PROPERTY NAME="seekable" GET="seekableGet" />
<PUBLIC:EVENT NAME="onloadstart" ID="onloadstart" />
<PUBLIC:EVENT NAME="onprogress" ID="onprogress" />
@@ -59,6 +62,7 @@
<PUBLIC:EVENT NAME="onratechange" ID="onratechange" />
<PUBLIC:EVENT NAME="ondurationchange" ID="ondurationchange" />
<PUBLIC:EVENT NAME="onvolumechange" ID="onvolumechange" />
+ <PUBLIC:EVENT NAME="onfallback" ID="onfallback" />
<PUBLIC:METHOD NAME="__fireMediaEvent" />
@@ -85,7 +89,8 @@
"ended":onended,
"ratechange":onratechange,
"durationchange":ondurationchange,
- "volumechange":onvolumechange
+ "volumechange":onvolumechange,
+ "onfallback":onfallback
},
initialProps = {};
@@ -102,6 +107,9 @@
function controlsGet() { return this.__controlsGet(); }
function volumeGet() { return this.__volumeGet(); }
function mutedGet() { return this.__mutedGet(); }
+ function bufferedGet() { return this.__bufferedGet(); }
+ function playedGet() { return this.__palyedGet(); }
+ function seekableGet() { return this.__seekableGet(); }
function srcSet(v) {
if (this.__srcSet) {
@@ -157,7 +165,7 @@
{
if (eventName in events) {
ev = createEventObject();
- this.__extendEvent.call(this, ev, arguments);
+ this.__extendEvent(ev, arguments);
events[eventName].fire(ev);
}
}
View
BIN  HtmlAudio.swf
Binary file not shown
View
1,385 HtmlMedia.js
@@ -20,17 +20,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-(function() {
- // Dont modify below here though!
+(function(w) {
var REGEXP_FILENAME_MP3 = /\.mp3(\?.*)?$/i,
REGEXP_MIMETYPE_MP3 = /^audio\/(?:x-)?(?:mp(?:eg|3))\s*;?/i,
REGEXP_MIMETYPE_FLV = /^video\/(?:x-)?(?:flv)\s*;?/i,
- HAS_NATIVE_AUDIO = false,
- HAS_NATIVE_VIDEO = false,
- USE_FALLBACK_MP3 = false,
- USE_FALLBACK_FLV = true,
- documentHead = document.getElementsByTagName("head")[0],
- isIE = !!document.attachEvent && Object.prototype.toString.call(window.opera) !== '[object Opera]',
+ HAS_NATIVE_AUDIO = !!w.HTMLAudioElement,
+ HAS_NATIVE_VIDEO = !!w.HTMLVideoElement,
+ USE_FALLBACK_MP3 = false,
+ USE_FALLBACK_FLV = true,
+ USE_FALLBACK_H264 = false,
+ HEAD = document.getElementsByTagName("head")[0],
+ isIE = !!document.attachEvent && Object.prototype.toString.call(w.opera) !== '[object Opera]',
MEDIA_EVENTS = [
"loadstart",
"progress",
@@ -53,7 +53,8 @@
"ended",
"ratechange",
"durationchange",
- "volumechange"
+ "volumechange",
+ "fallback" // Non-standard event fired when a node falls back to Flash
];
// Extends the properties of one 'source' Object onto 'destination'
@@ -62,425 +63,559 @@
destination[property] = source[property];
return destination;
}
+
+ /* Modified from http://gist.github.com/253174
+ *
+ * Detect if the browser can play a given codec with native
+ * HTML5 <audio> or <video>.
+ *
+ * @param aOrV - must be "audio" or "video", depending on the check
+ * @param mime - the mime type to test in "canPlayType"
+ * @param base64 - the base64 encoded data URI to use as the src for the test
+ * @param {function(boolean, Object|undefined)} callback
+ **/
+ function nativeCheck(aOrV, mime, base64, callback){
+ try {
+ var ele = document.createElement(aOrV);
+ // Shortcut which doesn't work in Chrome (always returns ""); pass through
+ // if "maybe" to do asynchronous check by loading MP3 data: URI
+ if(ele.canPlayType(mime) == "probably") {
+ callback(true);
+ return;
+ }
+
+ // If this event fires, then MP3s can be played
+ ele.addEventListener('loadedmetadata', function(e) {
+ callback(true);
+ }, false);
+
+ // If this is fired, then client can't play MP3s
+ ele.addEventListener('error', function(e){
+ callback(false, this.error)
+ }, false);
+
+ // Smallest base64-encoded MP3 I could come up with (<0.000001 seconds long)
+ ele.src = base64;
+ ele.load();
+ } catch(e) {
+ callback(false, e);
+ }
+ }
+
+ if (HAS_NATIVE_AUDIO) {
+ nativeCheck("audio", 'audio/mpeg; codecs="MP3"', "data:audio/mpeg;base64,/+MYxAAAAANIAAAAAExBTUUzLjk4LjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", function(canPlayMP3) {
+ USE_FALLBACK_MP3 = !canPlayMP3;
+ console.log("Native MP3 check complete: " + canPlayMP3);
+ });
+ nativeCheck("audio", 'audio/ogg; codecs="vorbis"', "data:audio/ogg;base64,T2dnUwACAAAAAAAAAABTkYlUAAAAABeKqR0BHgF2b3JiaXMAAAAAAUAfAAAAAAAAsDYAAAAAAACZAU9nZ1MAAAAAAAAAAAAAU5GJVAEAAACPWwFkCy3///////////%2B1A3ZvcmJpcx0AAABYaXBoLk9yZyBsaWJWb3JiaXMgSSAyMDA3MDYyMgAAAAABBXZvcmJpcxJCQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc%2B211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov%2B64u667t6roOhIasBADIAAAYhiGH3knMkFOQSSYpVcw5CKH1DjnlFGTSUsaYYoxRzpBTDDEFMYbQKYUQ1E45pQwiCENInWTOIEs96OBi5zgQGrIiAIgCAACMQYwhxpBzDEoGIXKOScggRM45KZ2UTEoorbSWSQktldYi55yUTkompbQWUsuklNZCKwUAAAQ4AAAEWAiFhqwIAKIAABCDkFJIKcSUYk4xh5RSjinHkFLMOcWYcowx6CBUzDHIHIRIKcUYc0455iBkDCrmHIQMMgEAAAEOAAABFkKhISsCgDgBAIMkaZqlaaJoaZooeqaoqqIoqqrleabpmaaqeqKpqqaquq6pqq5seZ5peqaoqp4pqqqpqq5rqqrriqpqy6ar2rbpqrbsyrJuu7Ks256qyrapurJuqq5tu7Js664s27rkearqmabreqbpuqrr2rLqurLtmabriqor26bryrLryratyrKua6bpuqKr2q6purLtyq5tu7Ks%2B6br6rbqyrquyrLu27au%2B7KtC7vourauyq6uq7Ks67It67Zs20LJ81TVM03X9UzTdVXXtW3VdW1bM03XNV1XlkXVdWXVlXVddWVb90zTdU1XlWXTVWVZlWXddmVXl0XXtW1Vln1ddWVfl23d92VZ133TdXVblWXbV2VZ92Vd94VZt33dU1VbN11X103X1X1b131htm3fF11X11XZ1oVVlnXf1n1lmHWdMLqurqu27OuqLOu%2BruvGMOu6MKy6bfyurQvDq%2BvGseu%2Brty%2Bj2rbvvDqtjG8um4cu7Abv%2B37xrGpqm2brqvrpivrumzrvm/runGMrqvrqiz7uurKvm/ruvDrvi8Mo%2BvquirLurDasq/Lui4Mu64bw2rbwu7aunDMsi4Mt%2B8rx68LQ9W2heHVdaOr28ZvC8PSN3a%2BAACAAQcAgAATykChISsCgDgBAAYhCBVjECrGIIQQUgohpFQxBiFjDkrGHJQQSkkhlNIqxiBkjknIHJMQSmiplNBKKKWlUEpLoZTWUmotptRaDKG0FEpprZTSWmopttRSbBVjEDLnpGSOSSiltFZKaSlzTErGoKQOQiqlpNJKSa1lzknJoKPSOUippNJSSam1UEproZTWSkqxpdJKba3FGkppLaTSWkmptdRSba21WiPGIGSMQcmck1JKSamU0lrmnJQOOiqZg5JKKamVklKsmJPSQSglg4xKSaW1kkoroZTWSkqxhVJaa63VmFJLNZSSWkmpxVBKa621GlMrNYVQUgultBZKaa21VmtqLbZQQmuhpBZLKjG1FmNtrcUYSmmtpBJbKanFFluNrbVYU0s1lpJibK3V2EotOdZaa0ot1tJSjK21mFtMucVYaw0ltBZKaa2U0lpKrcXWWq2hlNZKKrGVklpsrdXYWow1lNJiKSm1kEpsrbVYW2w1ppZibLHVWFKLMcZYc0u11ZRai621WEsrNcYYa2415VIAAMCAAwBAgAlloNCQlQBAFAAAYAxjjEFoFHLMOSmNUs45JyVzDkIIKWXOQQghpc45CKW01DkHoZSUQikppRRbKCWl1losAACgwAEAIMAGTYnFAQoNWQkARAEAIMYoxRiExiClGIPQGKMUYxAqpRhzDkKlFGPOQcgYc85BKRljzkEnJYQQQimlhBBCKKWUAgAAChwAAAJs0JRYHKDQkBUBQBQAAGAMYgwxhiB0UjopEYRMSielkRJaCylllkqKJcbMWomtxNhICa2F1jJrJcbSYkatxFhiKgAA7MABAOzAQig0ZCUAkAcAQBijFGPOOWcQYsw5CCE0CDHmHIQQKsaccw5CCBVjzjkHIYTOOecghBBC55xzEEIIoYMQQgillNJBCCGEUkrpIIQQQimldBBCCKGUUgoAACpwAAAIsFFkc4KRoEJDVgIAeQAAgDFKOSclpUYpxiCkFFujFGMQUmqtYgxCSq3FWDEGIaXWYuwgpNRajLV2EFJqLcZaQ0qtxVhrziGl1mKsNdfUWoy15tx7ai3GWnPOuQAA3AUHALADG0U2JxgJKjRkJQCQBwBAIKQUY4w5h5RijDHnnENKMcaYc84pxhhzzjnnFGOMOeecc4wx55xzzjnGmHPOOeecc84556CDkDnnnHPQQeicc845CCF0zjnnHIQQCgAAKnAAAAiwUWRzgpGgQkNWAgDhAACAMZRSSimllFJKqKOUUkoppZRSAiGllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimVUkoppZRSSimllFJKKaUAIN8KBwD/BxtnWEk6KxwNLjRkJQAQDgAAGMMYhIw5JyWlhjEIpXROSkklNYxBKKVzElJKKYPQWmqlpNJSShmElGILIZWUWgqltFZrKam1lFIoKcUaS0qppdYy5ySkklpLrbaYOQelpNZaaq3FEEJKsbXWUmuxdVJSSa211lptLaSUWmstxtZibCWlllprqcXWWkyptRZbSy3G1mJLrcXYYosxxhoLAOBucACASLBxhpWks8LR4EJDVgIAIQEABDJKOeecgxBCCCFSijHnoIMQQgghREox5pyDEEIIIYSMMecghBBCCKGUkDHmHIQQQgghhFI65yCEUEoJpZRSSucchBBCCKWUUkoJIYQQQiillFJKKSGEEEoppZRSSiklhBBCKKWUUkoppYQQQiillFJKKaWUEEIopZRSSimllBJCCKGUUkoppZRSQgillFJKKaWUUkooIYRSSimllFJKCSWUUkoppZRSSikhlFJKKaWUUkoppQAAgAMHAIAAI%2Bgko8oibDThwgMQAAAAAgACTACBAYKCUQgChBEIAAAAAAAIAPgAAEgKgIiIaOYMDhASFBYYGhweICIkAAAAAAAAAAAAAAAABE9nZ1MABAEAAAAAAAAAU5GJVAIAAABPWCgVAgEBAAA%3D", function(canPlayVorbis) {
+ console.log("Native OGG Vorbis check complete: " + canPlayVorbis);
+ });
+ } else {
+ //console.log("No native <audio> support. Using fallback...");
+ USE_FALLBACK_MP3 = true;
+ }
+
+ if (HAS_NATIVE_VIDEO) {
- function nativeCheckComplete(canPlayNativeMp3, error) {
+ } else {
+ //console.log("No native <video> support. Using fallback...");
+ USE_FALLBACK_FLV = true;
+ USE_FALLBACK_H264 = true;
+ }
- if (!canPlayNativeMp3) {
- USE_FALLBACK_MP3 = true;
-
- if (isIE) {
- // IE behaves strangely for html tags it doesn't recognize
- // like audio, video, and source. The workaround is to create
- // said node via JavaScript before the HTML parser finds them
- // in your HTML code.
- document.createElement("audio");
- document.createElement("video");
- document.createElement("source");
- }
+
+
+ if (isIE) {
+ // IE behaves strangely for html tags it doesn't recognize
+ // like audio, video, and source. The workaround is to create
+ // said node via JavaScript before the HTML parser finds them
+ // in your HTML code.
+ document.createElement("audio");
+ document.createElement("video");
+ document.createElement("source");
+ }
- // http://dev.w3.org/html5/spec/video.html#timeranges
- function TimeRanges() {
- this.length = 0;
- this.starts = [];
- this.ends = [];
- }
- TimeRanges.prototype = {
- start: function(index) {
- return this.starts[index];
- },
- end: function(index) {
- return this.ends[index];
- },
- add: function(start, end) {
- this.starts.push(start);
- this.ends.push(end);
- this.length++;
- }
- };
- if (!window.TimeRanges) window.TimeRanges = TimeRanges;
+ // http://dev.w3.org/html5/spec/video.html#timeranges
+ function TimeRanges() {
+ this.length = 0;
+ this.starts = [];
+ this.ends = [];
+ }
+ TimeRanges.prototype = {
+ start: function(index) {
+ return this.starts[index];
+ },
+ end: function(index) {
+ return this.ends[index];
+ },
+ add: function(start, end) {
+ this.starts.push(start);
+ this.ends.push(end);
+ this.length++;
+ }
+ };
+ if (!w.TimeRanges) w.TimeRanges = TimeRanges;
- // http://dev.w3.org/html5/spec/video.html#mediaerror
- function MediaError(code) {
- this.code = code;
- }
- MediaError.prototype = {
- code: -1,
- MEDIA_ERR_ABORTED: 1,
- MEDIA_ERR_NETWORK: 2,
- MEDIA_ERR_DECODE: 3,
- MEDIA_ERR_SRC_NOT_SUPPORTED: 4
- };
- if (!window.MediaError) window.MediaError = MediaError;
-
- // Browsers other than IE will use the W3C events model to fire media events
- function fireMediaEvent(eventName) {
- var ev = document.createEvent("Events"),
- func = this["on"+eventName];
- ev.initEvent(eventName, false, false);
- this.__extendEvent.call(this, ev, arguments);
- this.dispatchEvent(ev);
- if (typeof func === 'function') {
- func.call(this, ev);
+ // http://dev.w3.org/html5/spec/video.html#mediaerror
+ function MediaError(code) {
+ this.code = code;
+ }
+ MediaError.prototype = {
+ code: -1,
+ MEDIA_ERR_ABORTED: 1,
+ MEDIA_ERR_NETWORK: 2,
+ MEDIA_ERR_DECODE: 3,
+ MEDIA_ERR_SRC_NOT_SUPPORTED: 4
+ };
+ if (!w.MediaError) w.MediaError = MediaError;
+
+ // Browsers other than IE will use the W3C events model to fire media events
+ function fireMediaEvent(eventName) {
+ var ev = document.createEvent("Events"),
+ func = this["on"+eventName];
+ ev.initEvent(eventName, false, false);
+ this.__extendEvent(ev, arguments);
+ this.dispatchEvent(ev);
+ if (typeof func === 'function') {
+ func.call(this, ev);
+ }
+ }
+
+ function resourceSelectionAlgorithm() {
+ this.__networkState = this.NETWORK_NO_SOURCE;
+ // Asynchronously await a stable state
+ var mode;
+ var candidate;
+ if (this.__src) {
+ mode = "attribute";
+ } else {
+ var sources = this.getElementsByTagName("source");
+ for (var i=0; i<sources.length; i++) {
+ if (sources[i].parentNode === this) {
+ mode = "children";
+ candidate = sources[i];
+ break;
}
}
+ }
+ if (!mode) {
+ this.__networkState = this.NETWORK_EMPTY;
+ return; // Abort!
+ }
+ this.__networkState = this.NETWORK_LOADING;
+ this.__fireMediaEvent("loadstart");
- function resourceSelectionAlgorithm() {
- this.__networkState = this.NETWORK_NO_SOURCE;
- // Asynchronously await a stable state
- var mode;
- var candidate;
- if (this.src) {
- mode = "attribute";
- } else {
- var sources = this.getElementsByTagName("source");
- for (var i=0; i<sources.length; i++) {
- if (sources[i].parentNode === this) {
- mode = "children";
- candidate = sources[i];
- }
- }
- }
- if (!mode) {
- this.__networkState = this.NETWORK_EMPTY;
- return; // Abort!
- }
- this.__networkState = this.NETWORK_LOADING;
- this.__fireMediaEvent("timeupdate");
-
- if (mode === "attribute") {
+ if (mode === "attribute") {
+ this.__currentSrc = this.__src;
+ resourceFetchAlgorithm.call(this, this.__currentSrc);
+ } else { // the source elements will be used
+
+ }
+ }
+
+ function resourceFetchAlgorithm(url) {
+ if (!this.__fallbackId) {
+
+ // WTF? Properties on String are removed when createSound
+ // is called (FF2 on OSX, doesn't happen on Win)! Need to
+ // look into more deeply, ExternalInterface bug?
+ var string = extend({}, String);
- } else { // the source elements will be used
+ this.__fallbackId = w.HTMLAudioElement.__swf.__createSound(url, this.__volume, this.__muted);
+ // Copy the removed props from String back...
+ //extend(String, string);
+ for (var k in string) {
+ if (!String[k]) {
+ String[k] = string[k];
+ //console.log(k + " was missing from String!");
}
}
-
- function resourceFetchAlgorithm(url) {
+
+ if (!w.HTMLAudioElement.__swfSounds) w.HTMLAudioElement.__swfSounds = [];
+ w.HTMLAudioElement.__swfSounds.push(this);
+ } else {
+ w.HTMLAudioElement.__swf.__load(url);
+ }
+ }
+
+
+ // Accepts a HTMLElement with a settable 'src' property. Returns
+ // the element if it resolves relative paths to absolute of
+ // the current document, null otherwise.
+ function resolvesSrc(element) {
+ element.src="";
+ return element.src.indexOf(w.location.protocol) === 0 ? element : null;
+ }
+ // We send the absolute path to the fallback player, and need some
+ // native browser way of resolving relative URLs. Check a <script> first,
+ // since it won't make an HTTP request until it's placed in the DOM,
+ // some old browsers won't do that, so fall back to an <img>, which WILL
+ // fire an HTTP request instantly when it's 'src' is set (bad).
+ var RELATIVE_URL_RESOLVER = resolvesSrc(document.createElement("script")) || new Image();
+
+
+ function cloneNode(deep) {
+ var clone = this.__cloneNode(deep), nodeName = clone.nodeName.toLowerCase();
+ if (nodeName === "audio") return new HTMLAudioElement(clone);
+ if (nodeName === "video") return new HTMLVideoElement(clone);
+ //return clone; Should never happen
+ }
+ function setAttribute() {
+ var attr = arguments[0], value = arguments[1], rtn = this.__setAttribute(attr, value);
+ switch (attr) {
+ case "src":
+ this.src = value;
+ break;
+ case "preload":
+ break;
+ case "autoplay":
+ break;
+ case "loop":
+ break;
+ case "controls":
+ break;
+ }
+ return rtn;
+ }
+ function getAttribute() {
+ var rtn = this.__getAttribute(arguments[0]);
+ return rtn;
+ }
+ function defineGettersSetters(element) {
+ element.__defineGetter__("error", element.__errorGet);
+ element.__defineGetter__("src", element.__srcGet);
+ element.__defineGetter__("currentSrc", element.__currentSrcGet);
+ element.__defineGetter__("networkState",element.__networkStateGet);
+ element.__defineGetter__("readyState", element.__readyStateGet);
+ element.__defineGetter__("seeking", element.__seekingGet);
+ element.__defineGetter__("currentTime", element.__currentTimeGet);
+ element.__defineGetter__("startTime", element.__startTimeGet);
+ element.__defineGetter__("duration", element.__durationGet);
+ element.__defineGetter__("paused", element.__pausedGet);
+ element.__defineGetter__("ended", element.__endedGet);
+ element.__defineGetter__("controls", element.__controlsGet);
+ element.__defineGetter__("volume", element.__volumeGet);
+ element.__defineGetter__("muted", element.__mutedGet);
+ element.__defineGetter__("buffered", element.__bufferedGet);
+ element.__defineGetter__("played", element.__playedGet);
+ element.__defineGetter__("seekable", element.__seekableGet);
+
+ element.__defineSetter__("src", element.__srcSet);
+ element.__defineSetter__("currentTime", element.__currentTimeSet);
+ element.__defineSetter__("controls", element.__controlsSet);
+ element.__defineSetter__("volume", element.__volumeSet);
+ element.__defineSetter__("muted", element.__mutedSet);
+ }
+ function HTMLMediaElement(element) {
+ if (element) {
+ // Copy the properties from 'this' to the 'element'
+ extend(element, this);
+
+ if (!isIE) {
+ // Browser's other than IE need to get the internally
+ // used '__fireMediaEvent' here. IE gets it from the HTC file.
+ element.__fireMediaEvent = fireMediaEvent;
+ defineGettersSetters(element);
+
+ // http://dev.w3.org/html5/spec-author-view/common-microsyntaxes.html#boolean-attributes
+ var src = element.getAttribute("src"),
+ preload = element.getAttribute("preload"),
+ autoplay = element.getAttribute("autoplay"),
+ loop = element.getAttribute("loop"),
+ controls = element.getAttribute("controls"),
+ i=0,
+ l=MEDIA_EVENTS.length, eventName, eventCode;
+ if (src) element.src = src;
+ if (preload) element.preload = preload;
+ if (autoplay) element.autoplay = !!autoplay;
+ if (loop) element.loop = !!loop;
+ if (controls) element.controls = !!controls;
+ // Register all the media events from inital element
+ for (; i<l; i++) {
+ eventName = "on"+MEDIA_EVENTS[i];
+ eventCode = element.getAttribute(eventName);
+ if (eventCode && !element[eventName]) {
+ //element[eventName] = new Function("event", eventCode);
+ element[eventName] = eval("(function() { return function "+eventName+"(event) { "+eventCode+" }; })()");
+ }
+ }
+ } else {
+ // Add the behavior to the element. The element does not
+ // need to be appended to the document when used with the
+ // 'addBehavior' method.
+ element.addBehavior(w.HTMLAudioElement.htcPath);
}
+
+ // The cloneNode function needs to call the appropriate
+ // HTMLMediaElement constructor as well.
+ if (!element.__cloneNode) element.__cloneNode = element.cloneNode;
+ element.cloneNode = cloneNode;
+ // getAttribute and setAttribute need to look out for
+ // specific cases (loop, src, etc.)
+ if (!element.__setAttribute) element.__setAttribute = element.setAttribute;
+ element.setAttribute = setAttribute;
+ if (!element.__getAttribute) element.__getAttribute = element.getAttribute;
+ element.getAttribute = getAttribute;
+ }
+ }
+ HTMLMediaElement.prototype = {
+
+ // readonly attribute MediaError error;
+ __error: null,
+ __errorGet: function() { return this.__error; },
+
+ // attribute DOMString src;
+ __src: "",
+ __srcGet: function() { return this.__src; },
+ __srcSet: function(src) {
+ if (src !== "" && src.indexOf(':') < 0) {
+ RELATIVE_URL_RESOLVER.src = src;
+ src = RELATIVE_URL_RESOLVER.src;
+ }
+ this.__src = src;
+ },
+
+ // readonly attribute DOMString currentSrc;
+ __currentSrc: "",
+ __currentSrcGet: function() { return this.__currentSrc; },
+ // network state
+ NETWORK_EMPTY: 0,
+ NETWORK_IDLE: 1,
+ NETWORK_LOADING: 2,
+ NETWORK_LOADED: 3,
+ NETWORK_NO_SOURCE: 4,
- // There's two cases for HTMLMediaElement: the user agent natively
- // implements it or it doesn't. If it does, then we need to augment the
- // functions in the prototype to use our wrapper if required.
- if (window.HTMLMediaElement) {
- } else {
+ // readonly attribute unsigned short networkState;
+ __networkState: 0,
+ __networkStateGet: function() { return this.__networkState; },
+
+ // attribute DOMString preload;
+ preload: null,
+
+ // readonly attribute TimeRanges buffered;
+ __bufferedGet: function() {
-
+ },
+
+ // void load();
+ load: function() {
+ if (this.__networkState === this.NETWORK_LOADING || this.__networkState === this.NETWORK_IDLE) {
+ this.__fireMediaEvent("abort");
+ }
+ if (this.__networkState !== this.NETWORK_EMPTY) {
+ this.__networkState = this.NETWORK_EMPTY;
+ this.__readyState = this.HAVE_NOTHING;
+ this.__paused = true;
+ this.__seeking = false;
+ this.__fireMediaEvent("emptied");
+ }
+ this.playbackRate = this.defaultPlaybackRate;
+ this.__error = null;
+ resourceSelectionAlgorithm.call(this);
+ },
+
+ // DOMString canPlayType(in DOMString type);
+ canPlayType: function() {
+ return "";
+ },
+
+ // ready state
+ HAVE_NOTHING: 0,
+ HAVE_METADATA: 1,
+ HAVE_CURRENT_DATA: 2,
+ HAVE_FUTURE_DATA: 3,
+ HAVE_ENOUGH_DATA: 4,
+
+ // readonly attribute unsigned short readyState;
+ __readyState: 0,
+ __readyStateGet: function() { return this.__readyState; },
+
+ // readonly attribute boolean seeking;
+ __seeking: false,
+ __seekingGet: function() { return this.__seeking; },
+
+ // attribute float currentTime;
+ __currentTimeGet: function() {
+ return this.__fallbackId != undefined ?
+ w.HTMLAudioElement.__swf.__getCurrentTime(this.__fallbackId) :
+ 0;
+ },
+ __currentTimeSet: function(time) {
+ if (this.__readyState === this.HAVE_NOTHING) {
+ throw new Error("INVALID_STATE_ERR: DOM Exception 11");
+ }
+ // TODO: Abort any already running 'seeking' instances
+ this.__seeking = true;
+ w.HTMLAudioElement.__swf.__setCurrentTime(this.__fallbackId, time);
+ this.__fireMediaEvent("timeupdate");
+ },
+
+ //readonly attribute float startTime;
+ __startTime: 0.0,
+ __startTimeGet: function() { return this.__startTime; },
+
+ //readonly attribute float duration;
+ __duration: NaN,
+ __durationGet: function() { return this.__duration; },
+
+ //readonly attribute boolean paused;
+ __paused: true,
+ __pausedGet: function() { return this.__paused; },
+
+ // attribute float defaultPlaybackRate;
+ defaultPlaybackRate: 1.0,
+ // attribute float playbackRate;
+ playbackRate: 1.0,
- // Accepts a HTMLElement with a settable 'src' property. Returns
- // the element if it resolves relative paths to absolute of
- // the current document, null otherwise.
- function resolvesSrc(element) {
- element.src="";
- return element.src.indexOf(window.location.protocol) === 0 ? element : null;
- }
- // We send the absolute path to the fallback player, and need some
- // native browser way of resolving relative URLs. Check a <script> first,
- // since it won't make an HTTP request until it's placed in the DOM,
- // some old browsers won't do that, so fall back to an <img>, which WILL
- // fire an HTTP request when it's 'src' is set (bad).
- var RELATIVE_URL_RESOLVER = resolvesSrc(document.createElement("script")) || new Image();
+ // readonly attribute TimeRanges played;
+ __playedGet: function() {
+
+ },
+
+ // readonly attribute TimeRanges seekable;
+ __seekableGet: function() {
+
+ },
+ // readonly attribute boolean ended;
+ __ended: false,
+ __endedGet: function() { return this.__ended; },
+ // attribute boolean autoplay;
+ autoplay: false,
- function cloneNode(deep) {
- var clone = this.__cloneNode(deep), nodeName = clone.nodeName.toLowerCase();
- if (nodeName === "audio") return new HTMLAudioElement(clone);
- if (nodeName === "video") return new HTMLVideoElement(clone);
- //return clone; Should never happen
- }
- function setAttribute() {
- var attr = arguments[0], value = arguments[1], rtn = this.__setAttribute(attr, value);
- switch (attr) {
- case "src":
- this.src = value;
- break;
- case "preload":
- break;
- case "autoplay":
- break;
- case "loop":
- break;
- case "controls":
- break;
- }
- return rtn;
- }
- function getAttribute() {
- var rtn = this.__getAttribute(arguments[0]);
- return rtn;
+ // attribute boolean loop;
+ loop: false,
+
+ // void play();
+ play: function() {
+ if (this.__networkState === this.NETWORK_EMPTY) {
+ resourceSelectionAlgorithm.call(this);
+ }
+ if (this.__ended && this.playbackRate >= 0) {
+ this.currentTime = this.startTime;
+ }
+ if (this.__paused === true) {
+ this.__paused = false;
+ this.__fireMediaEvent("play");
+ if (this.__readyState === this.HAVE_NOTHING || this.__readyState === this.HAVE_METADATA || this.__readyState === this.HAVE_CURRENT_DATA) {
+ this.__fireMediaEvent("waiting");
+ } else {
+ this.__fireMediaEvent("playing");
}
- function defineGettersSetters(element) {
- element.__defineGetter__("error", element.__errorGet);
- element.__defineGetter__("src", element.__srcGet);
- element.__defineGetter__("currentSrc", element.__currentSrcGet);
- element.__defineGetter__("networkState",element.__networkStateGet);
- element.__defineGetter__("readyState", element.__readyStateGet);
- element.__defineGetter__("seeking", element.__seekingGet);
- element.__defineGetter__("currentTime", element.__currentTimeGet);
- element.__defineGetter__("startTime", element.__startTimeGet);
- element.__defineGetter__("duration", element.__durationGet);
- element.__defineGetter__("paused", element.__pausedGet);
- element.__defineGetter__("ended", element.__endedGet);
- element.__defineGetter__("controls", element.__controlsGet);
- element.__defineGetter__("volume", element.__volumeGet);
- element.__defineGetter__("muted", element.__mutedGet);
-
- element.__defineSetter__("src", element.__srcSet);
- element.__defineSetter__("currentTime", element.__currentTimeSet);
- element.__defineSetter__("controls", element.__controlsSet);
- element.__defineSetter__("volume", element.__volumeSet);
- element.__defineSetter__("muted", element.__mutedSet);
+ w.HTMLAudioElement.__swf.__play(this.__fallbackId);
+ }
+ },
+
+ // void pause();
+ pause: function() {
+ if (this.__networkState === this.NETWORK_EMPTY) {
+ resourceSelectionAlgorithm.call(this);
+ }
+ if (this.__paused === false) {
+ this.__paused = true;
+ this.__fireMediaEvent("timeupdate");
+ this.__fireMediaEvent("pause");
+ w.HTMLAudioElement.__swf.__pause(this.__fallbackId);
+ }
+ },
+
+ // attribute boolean controls;
+ __controls: false,
+ __controlsGet: function() { return this.__controls; },
+ __controlsSet: function(bool) {
+ this.__controls = bool;
+ },
+
+ // attribute float volume;
+ __volume: 1.0,
+ __volumeGet: function() { return this.__volume; },
+ __volumeSet: function(vol) {
+ if (vol > 1 || vol < 0) {
+ throw new Error("INDEX_SIZE_ERROR: DOM Exception 1");
+ } else if (this.__volume !== vol) {
+ this.__volume = vol;
+ if (this.__fallbackId != undefined) {
+ w.HTMLAudioElement.__swf.__setVolume(this.__fallbackId, vol);
}
- function HTMLMediaElement(element) {
- if (element) {
-
- // Copy the properties from 'this' to the 'element'
- extend(element, this);
-
- if (!isIE) {
- // Browser's other than IE need to get the internally
- // used '__fireMediaEvent' here. IE gets it from the HTC file.
- element.__fireMediaEvent = fireMediaEvent;
- defineGettersSetters(element);
-
- // http://dev.w3.org/html5/spec-author-view/common-microsyntaxes.html#boolean-attributes
- var src = element.getAttribute("src"),
- preload = element.getAttribute("preload"),
- autoplay = element.getAttribute("autoplay"),
- loop = element.getAttribute("loop"),
- controls = element.getAttribute("controls"),
- i=0,
- l=MEDIA_EVENTS.length, eventName, eventCode;
- if (src) element.src = src;
- if (preload) element.preload = preload;
- if (autoplay) element.autoplay = !!autoplay;
- if (loop) element.loop = !!loop;
- if (controls) element.controls = !!controls;
-
- // Register all the media events from inital element
- for (; i<l; i++) {
- eventName = "on"+MEDIA_EVENTS[i];
- eventCode = element.getAttribute(eventName);
- if (eventCode) {
- element[eventName] = new Function("event", eventCode);
- }
- }
- } else {
- // Add the behavior to the element. The element does not
- // need to be appended to the document when used with the
- // 'addBehavior' method.
- element.addBehavior(HTMLMediaElement.htcPath);
- }
-
- // The cloneNode function needs to call the appropriate
- // HTMLMediaElement constructor as well.
- if (!element.__cloneNode) element.__cloneNode = element.cloneNode;
- element.cloneNode = cloneNode;
- // getAttribute and setAttribute need to look out for
- // specific cases (loop, src, etc.)
- if (!element.__setAttribute) element.__setAttribute = element.setAttribute;
- element.setAttribute = setAttribute;
- if (!element.__getAttribute) element.__getAttribute = element.getAttribute;
- element.getAttribute = getAttribute;
- }
+ this.__fireMediaEvent("volumechange");
+ }
+ },
+
+ // attribute boolean muted;
+ __muted: false,
+ __mutedGet: function() { return this.__muted; },
+ __mutedSet: function(muted) {
+ muted = !!muted;
+ if (this.__muted != muted) {
+ this.__muted = muted;
+ if (this.__fallbackId != undefined) {
+ w.HTMLAudioElement.__swf.__setMuted(this.__fallbackId, muted);
}
- HTMLMediaElement.prototype = {
-
- // readonly attribute MediaError error;
- __error: null,
- __errorGet: function() { return this.__error; },
-
- // attribute DOMString src;
- __src: "",
- __srcGet: function() { return this.__src; },
- __srcSet: function(src) {
- if (src !== "" && src.indexOf(':') < 0) {
- RELATIVE_URL_RESOLVER.src = src;
- src = RELATIVE_URL_RESOLVER.src;
- }
- this.__src = src;
- },
-
- // readonly attribute DOMString currentSrc;
- __currentSrc: "",
- __currentSrcGet: function() { return this.__currentSrc; },
-
- // network state
- NETWORK_EMPTY: 0,
- NETWORK_IDLE: 1,
- NETWORK_LOADING: 2,
- NETWORK_LOADED: 3,
- NETWORK_NO_SOURCE: 4,
-
- // readonly attribute unsigned short networkState;
- __networkState: 0,
- __networkStateGet: function() { return this.__networkState; },
-
- // attribute DOMString preload;
- preload: null,
-
- // readonly attribute TimeRanges buffered;
- buffered: null,
-
- // void load();
- load: function() {
- // Overridden by HTMLAudioElement & HTMLVideoElement
- },
-
- // DOMString canPlayType(in DOMString type);
- canPlayType: function() {
- return "";
- },
-
- // ready state
- HAVE_NOTHING: 0,
- HAVE_METADATA: 1,
- HAVE_CURRENT_DATA: 2,
- HAVE_FUTURE_DATA: 3,
- HAVE_ENOUGH_DATA: 4,
-
- // readonly attribute unsigned short readyState;
- __readyState: 0,
- __readyStateGet: function() { return this.__readyState; },
-
- // readonly attribute boolean seeking;
- __seeking: false,
- __seekingGet: function() { return this.__seeking; },
-
- // attribute float currentTime;
- __currentTimeGet: function() {
- return this.__fallbackId != undefined ?
- HTMLAudioElement.__swf.__getCurrentTime(this.__fallbackId) :
- 0;
- },
- __currentTimeSet: function(time) {
- if (this.__readyState === this.HAVE_NOTHING) {
- throw new Error("INVALID_STATE_ERR: DOM Exception 11");
- }
- // TODO: Abort any already running 'seeking' instances
- this.__seeking = true;
- this.__fireMediaEvent("timeupdate");
- HTMLAudioElement.__swf.__setCurrentTime(this.__fallbackId, time);
- },
-
-
- //readonly attribute float startTime;
- __startTime: 0.0,
- __startTimeGet: function() { return this.__startTime; },
-
- //readonly attribute float duration;
- __duration: NaN,
- __durationGet: function() { return this.__duration; },
-
- //readonly attribute boolean paused;
- __paused: true,
- __pausedGet: function() { return this.__paused; },
-
- // attribute float defaultPlaybackRate;
- defaultPlaybackRate: 1.0,
-
- // attribute float playbackRate;
- playbackRate: 1.0,
-
- // readonly attribute TimeRanges played;
- played: null,
-
- // readonly attribute TimeRanges seekable;
- seekable: null,
-
- // readonly attribute boolean ended;
- __ended: false,
- __endedGet: function() { return this.__ended; },
-
- // attribute boolean autoplay;
- autoplay: false,
-
- // attribute boolean loop;
- loop: false,
-
- // void play();
- play: function() {
-
- },
-
- // void pause();
- pause: function() {
-
- },
-
- // attribute boolean controls;
- __controls: false,
- __controlsGet: function() { return this.__controls; },
- __controlsSet: function(bool) {
- this.__controls = bool;
- },
-
- // attribute float volume;
- __volume: 1.0,
- __volumeGet: function() { return this.__volume; },
- __volumeSet: function(vol) {
- if (vol > 1 || vol < 0) {
- throw new Error("INDEX_SIZE_ERROR: DOM Exception 1");
- } else if (this.__volume !== vol) {
- this.__volume = vol;
- if (this.__fallbackId != undefined) {
- HTMLAudioElement.__swf.__setVolume(this.__fallbackId, vol);
- }
- this.__fireMediaEvent("volumechange");
- }
- },
+ this.__fireMediaEvent("volumechange");
+ }
+ },
+
+ __endedCallback: function() {
+ if (this.loop) {
+ this.currentTime = this.startTime;
+ } else {
+ this.__ended = true;
+ this.__fireMediaEvent("timeupdate");
+ this.__fireMediaEvent("ended");
+ }
+ },
+ __errorCallback: function() {
- // attribute boolean muted;
- __muted: false,
- __mutedGet: function() { return this.__muted; },
- __mutedSet: function(muted) {
- muted = !!muted;
- if (this.__muted != muted) {
- this.__muted = muted;
- // TODO: Send mute message to fallback
- this.__fireMediaEvent("volumechange");
- }
- },
+ },
+ __metadataCallback: function(duration) {
+ this.__readyState = this.HAVE_METADATA;
+ this.__duration = duration;
+ this.__fireMediaEvent("durationchange");
+ this.__fireMediaEvent("loadedmetadata");
+ this.currentTime = this.startTime;
+ },
+ __seekedCallback: function() {
- __endedCallback: function() {
- if (this.loop) {
- this.currentTime = this.startTime;
- this.play();
- } else {
- this.__ended = true;
- this.__fireMediaEvent("timeupdate");
- this.__fireMediaEvent("ended");
- }
- },
- __errorCallback: function() {
-
- },
- __seekedCallback: function() {
-
- },
- __extendEvent: function(ev, args) {
- var eventName = args[0];
- if (eventName === "progress") {
- // Copying Firefox's behavior by specifying
- // the bytes so far for 'progress' events
- ev.lengthComputable = true;
- ev.loaded = args[1];
- ev.total = args[2];
- }
- },
-
- // This is a simple boolean that the developer can check
- // to determine whether or not this media element is native
- // to the browser, or using the fallback mechanism.
- isNative: false,
- toString: function() {
- return "[object HTMLMediaElement]";
- }
- };
- window.HTMLMediaElement = HTMLMediaElement;
+ },
+ __extendEvent: function(ev, args) {
+ var eventName = args[0];
+ if (eventName === "progress") {
+ // Copying Firefox's behavior by specifying
+ // the bytes so far for 'progress' events
+ ev.lengthComputable = true;
+ ev.loaded = args[1];
+ ev.total = args[2];
}
+ },
+
+ // This is a simple boolean that the developer can check
+ // to determine whether or not this media element is native
+ // to the browser, or has been "converted" to a "fallback node".
+ isNative: false,
+ toString: function() {
+ return "[object HTMLMediaElement]";
+ }
+ };
+ if (w.HTMLMediaElement) {
+ var nativeMedia = w.HTMLMediaElement;
+ extend(nativeMedia.prototype, {
+ __checkError: function() {
+ var isUnsupported = this.error.code == 4;
+ if (isUnsupported && REGEXP_FILENAME_MP3.test(this.currentSrc || this.src)) {
+ this.removeEventListener("error", this.__checkError, false);
+ new HTMLAudioElement(this);
+ }
+ }
+ });
+
+ } else {
+ w.HTMLMediaElement = HTMLMediaElement;
+ }
@@ -490,273 +625,207 @@
- // If the user agent has HTMLAudioElement defined in the window,
- // then we must augment some prototypes, and overwrite a few
- // functions to transparently use the Flash fallback when needed.
- if (window.HTMLAudioElement) {
- HAS_NATIVE_AUDIO = true;
-
- HTMLAudioElement.prototype.isNative = true;
-
- var nativeLoad = HTMLAudioElement.prototype.load;
- HTMLAudioElement.prototype.load = function() {
- console.log("calling overriden HTMLAudioElement#load");
- nativeLoad.apply(this, arguments);
- }
+ function HTMLAudioElement() {
+ HTMLMediaElement.apply(this, arguments);
+ var ele = arguments[0];
+ ele.__fireMediaEvent("fallback");
+ return ele;
+ }
+ HTMLAudioElement.prototype = new HTMLMediaElement;
+ extend(HTMLAudioElement.prototype, {
+ toString: function() {
+ return "[object HTMLAudioElement]";
+ }
+ });
- } else {
- // User agent hasn't implemented HTMLAudioElement, we must
- // implement our own with regular JS, following the spec.
- function HTMLAudioElement() {
- HTMLMediaElement.apply(this, arguments);
- return arguments[0];
- }
- HTMLAudioElement.prototype = new HTMLMediaElement;
- extend(HTMLAudioElement.prototype, {
- toString: function() {
- return "[object HTMLAudioElement]";
- },
- load: function() {
- if (this.__networkState === this.NETWORK_LOADING || this.__networkState === this.NETWORK_IDLE) {
- this.__fireMediaEvent("abort");
- }
- if (this.__networkState !== this.NETWORK_EMPTY) {
- this.__networkState = this.NETWORK_EMPTY;
- this.__readyState = this.HAVE_NOTHING;
- this.__paused = true;
- this.__seeking = false;
- this.__fireMediaEvent("emptied");
- }
- this.playbackRate = this.defaultPlaybackRate;
- this.__error = null;
- resourceSelectionAlgorithm.call(this);
-
-
- //this.__currentSrc = this.src;
- if (!this.__fallbackId) {
-
- // WTF? Properties on String are removed when createSound
- // is called (FF2 on OSX, doesn't happen on Win)! Need to
- // look into more deeply, ExternalInterface bug?
- var string = extend({}, String);
-
- this.__fallbackId = HTMLAudioElement.__swf.__createSound(this.src, this.volume);
-
- // Copy the removed props from String back...
- //extend(String, string);
- for (var k in string) {
- if (!String[k]) {
- String[k] = string[k];
- console.log(k + " was missing from String!");
- }
- }
-
- if (!HTMLAudioElement.__swfSounds) HTMLAudioElement.__swfSounds = [];
- HTMLAudioElement.__swfSounds.push(this);
- } else {
- HTMLAudioElement.__swf.__load(this.src);
- }
- },
- play: function() {
- if (this.__networkState === this.NETWORK_EMPTY) {
- this.load();
- }
- if (this.__ended && this.playbackRate >= 0) {
- this.currentTime = this.startTime;
- }
- if (this.__paused === true) {
- this.__paused = false;
- this.__fireMediaEvent("play");
- if (this.__readyState === this.HAVE_NOTHING || this.__readyState === this.HAVE_METADATA || this.__readyState === this.HAVE_CURRENT_DATA) {
- this.__fireMediaEvent("waiting");
- } else {
- this.__fireMediaEvent("playing");
- }
- HTMLAudioElement.__swf.__play(this.__fallbackId);
- }
- },
- pause: function() {
- if (this.__networkState === this.NETWORK_EMPTY) {
- this.load();
- }
- if (this.__paused === false) {
- this.__paused = true;
- this.__fireMediaEvent("timeupdate");
- this.__fireMediaEvent("pause");
- HTMLAudioElement.__swf.__pause(this.__fallbackId);
- }
- }
- });
- window.HTMLAudioElement = HTMLAudioElement;
-
-
- // Make 'document.createElement()' return proper <audio> nodes
- var nativeCreateElement = document.createElement, useApply = true;
- try {
- nativeCreateElement.apply(document, ["div"]);
- } catch(e) {
- // IE6 doesn't like calling this with 'apply',
- // but works fine if called directly.
- useApply = false;
- }
- document.createElement = function() {
- var ele = useApply ? nativeCreateElement.apply(this, arguments) : nativeCreateElement(arguments[0]),
- nodeName = ele.nodeName.toLowerCase();
- if (nodeName === "audio") new HTMLAudioElement(ele);
- return ele;
- };
-
-
-
- // The HTMLAudioElement has a convience constructor: Audio.
- function Audio() {
- //if (!(this instanceof arguments.callee)) {
- // throw new TypeError("DOM object constructor cannot be called as a function.");
- //}
- var a = document.createElement("audio"), src = arguments[0];
- a.preload = "auto";
- if (src !== undefined) a.src = String(src);
- return a;
- }
- Audio.prototype = HTMLAudioElement.prototype;
- window.Audio = Audio;
- }
- // We want to augment both the native and our custom 'canPlayType'
- // functions to check the argument for valid MP3 mime-types.
- var origCanPlayType = HTMLAudioElement.prototype.canPlayType;
- function canPlayType() {
- if (REGEXP_MIMETYPE_MP3.test(arguments[0])) return "probably";
- return origCanPlayType.apply(this, arguments);
- }
- HTMLAudioElement.prototype.canPlayType = canPlayType;
-
-
-
- // Embed the fallback SWF into the page
- function embedSwf() {
- console.log("embedding SWF at: " + HTMLAudioElement.swfPath);
- var container = document.createElement("div"),
- id = "HtmlAudio",
- flashvars = {},
- params = {
- allowScriptAccess: "always"
- },
- attributes = {
- style: "position:fixed; top:0px; right:0px;"
- };
- container.id = id;
- document.body.appendChild(container);
- swfobject.embedSWF(HTMLAudioElement.swfPath, id, 1, 1, "10", false, flashvars, params, attributes);
- }
-
- function swfLoaded() {
- console.log("'HtmlAudio.swf' embedded, called from EI");
- // TODO: Process fallback queue.
+ if (HAS_NATIVE_AUDIO) {
+ var nativeAudio = w.HTMLAudioElement,
+ nativeLoad = nativeAudio.prototype.load;
+ extend(nativeAudio.prototype, {
+ isNative: true,
+ load: function() {
+ //console.log("calling overriden HTMLAudioElement#load");
+ nativeLoad.apply(this, arguments);
}
- HTMLAudioElement.__swfLoaded = swfLoaded;
+ });
+ } else {
+
+ w.HTMLAudioElement = HTMLAudioElement;
+
+
+ // Make 'document.createElement()' return proper <audio> nodes
+ var nativeCreateElement = document.createElement, useApply = true;
+ try {
+ nativeCreateElement.apply(document, ["div"]);
+ } catch(e) {
+ // IE6 doesn't like calling this with 'apply',
+ // but works fine if called directly.
+ useApply = false;
+ }
+ document.createElement = function() {
+ var ele = useApply ? nativeCreateElement.apply(this, arguments) : nativeCreateElement(arguments[0]),
+ nodeName = ele.nodeName.toLowerCase();
+ if (nodeName === "audio") new HTMLAudioElement(ele);
+ return ele;
+ };
+
+
+
+ // The HTMLAudioElement has a convience constructor: Audio.
+ function Audio() {
+ //if (!(this instanceof arguments.callee)) {
+ // throw new TypeError("DOM object constructor cannot be called as a function.");
+ //}
+ var a = document.createElement("audio"), src = arguments[0];
+ a.preload = "auto";
+ if (src !== undefined) a.src = String(src);
+ return a;
+ }
+ Audio.prototype = HTMLAudioElement.prototype;
+ w.Audio = Audio;
+ }
+ // We want to augment both the native and our custom 'canPlayType'
+ // functions to check the argument for valid MP3 mime-types.
+ var origCanPlayType = HTMLAudioElement.prototype.canPlayType;
+ function canPlayType() {
+ if (REGEXP_MIMETYPE_MP3.test(arguments[0])) return "probably";
+ return origCanPlayType.apply(this, arguments);
+ }
+ HTMLAudioElement.prototype.canPlayType = canPlayType;
+
+
+
+
+ function HTMLVideoElement() {
+ HTMLMediaElement.apply(this, arguments);
+ return arguments[0];
+ }
+ HTMLVideoElement.prototype = new HTMLMediaElement;
+ extend(HTMLVideoElement.prototype, {
+ toString: function() {
+ return "[object HTMLVideoElement]";
+ }
+ });
+ if (HAS_NATIVE_VIDEO) {
+ var nativeVideo = w.HTMLVideoElement;
+ nativeVideo.prototype.isNative = true;
+ } else {
+ w.HTMLVideoElement = HTMLVideoElement;
+ }
+
+
+ w.HTMLMediaElement.setPath = function(path) {
+ // First ensure the path ends with a '/'
+ var d = path.length - 1;
+ if (!(d >= 0 && path.indexOf('/', d) === d)) {
+ path = path + '/';
+ }
+ extend(w.HTMLAudioElement, {
+ htcPath: path + "HtmlAudio.htc",
+ swfPath: path + "HtmlAudio.swf"
+ });
+ extend(w.HTMLVideoElement, {
+ htcPath: path + "HtmlVideo.htc",
+ swfPath: path + "HtmlVideo.swf"
+ });
+ }
- function fixHtmlTags() {
- var audioNodes = document.getElementsByTagName("audio"),
- videoNodes = document.getElementsByTagName("video"), i, node;
- for (i=0; i<audioNodes.length; i++) {
- node = audioNodes[i];
- if (!HAS_NATIVE_AUDIO) {
- new HTMLAudioElement(node);
- node.removeAttribute("_moz-userdefined");
- } else {
- //console.log(node.error);
- //HTMLAudioElement.__attemptFallback(audioNodes[i]);
- }
+ // Embed the fallback SWF into the page
+ function embedSwf() {
+ console.log("embedding SWF at: " + w.HTMLAudioElement.swfPath);
+ var container = document.createElement("div"),
+ id = "HtmlAudio",
+ flashvars = {},
+ params = {
+ allowScriptAccess: "always"
+ },
+ attributes = {
+ style: "position:fixed; top:0px; right:0px;"
+ };
+ container.id = id;
+ document.body.appendChild(container);
+ swfobject.embedSWF(w.HTMLAudioElement.swfPath, id, 1, 1, "10", false, flashvars, params, attributes);
+ }
+
+ function swfLoaded() {
+ console.log("'HtmlAudio.swf' embedded, called from EI");
+ // TODO: Process fallback queue.
+ }
+ w.HTMLAudioElement.__swfLoaded = swfLoaded;
+
+ function fixHtmlTags() {
+ var audioNodes = document.getElementsByTagName("audio"),
+ videoNodes = document.getElementsByTagName("video"), i, node;
+ for (i=0; i<audioNodes.length; i++) {
+ node = audioNodes[i];
+ if (!HAS_NATIVE_AUDIO) {
+ new HTMLAudioElement(node);
+ node.removeAttribute("_moz-userdefined");
+ } else {
+ if (node.error) {
+ node.__checkError();
+ } else {
+ node.addEventListener("error", node.__checkError, false);
}
+ //HTMLAudioElement.__attemptFallback(audioNodes[i]);
+ }
+ }
- for (i=0; i<videoNodes.length; i++) {
- node = videoNodes[i];
- console.log(node.error);
- }
+ for (i=0; i<videoNodes.length; i++) {
+ node = videoNodes[i];
+ if (!HAS_NATIVE_VIDEO) {
+ new HTMLVideoElement(node);
+ node.removeAttribute("_moz-userdefined");
+ } else {
+
}
+ }
+ }
-
-
- function init() {
- if (arguments.callee.done) return;
- arguments.callee.done = true;
-
- if (isIE) {
- // For IE to fire custom events and use real getters and setters,
- // they must be defined in an HTC file and applied via CSS 'behavior'.
- var style = document.createElement("style");
- style.type = "text/css";
- style.styleSheet.cssText = "audio, video { behavior:url("+HTMLMediaElement.htcPath+"); }";
- documentHead.appendChild(style);
- }
- embedSwf();
- fixHtmlTags();
- }
- if (document.addEventListener) {
- document.addEventListener('DOMContentLoaded', init, false);
- }
- (function() {
- /*@cc_on
- try {
- document.body.doScroll('up');
- return init();
- } catch(e) {}
- /*@if (false) @*/
- if (/loaded|complete/.test(document.readyState)) return init();
- /*@end @*/
- if (!init.done) setTimeout(arguments.callee, 30);
- })();
-
- if (window.addEventListener) {
- window.addEventListener('load', init, false);
- } else if (window.attachEvent) {
- window.attachEvent('onload', init);
- }
- } else {
- /* If we get here, then the user agent natively supports both
- * the HTML5 Audio API, as well as MP3 decoding support, so this
- * script can bail and do nothing!
- */
+
+ function init() {
+ if (arguments.callee.done) return;
+ arguments.callee.done = true;
+
+ if (isIE) {
+ // For IE to fire custom events and use real getters and setters,
+ // they must be defined in an HTC file and applied via CSS 'behavior'.
+ var style = document.createElement("style");
+ style.type = "text/css";
+ style.styleSheet.cssText = "audio, video { behavior:url("+w.HTMLAudioElement.htcPath+"); } video { behavior:url("+w.HTMLVideoElement.htcPath+"); }";
+ HEAD.appendChild(style);
}
+ embedSwf();
+ fixHtmlTags();
}
- /* From http://gist.github.com/253174
- *
- * Detect if the browser can play MP3 audio using native HTML5 Audio.
- * Invokes the callack function with first parameter is the boolean success
- * value; if that value is false, a second error parameter is passed. This error
- * is either HTMLMediaError or some other DOMException or Error object.
- * Note the callback is likely to be invoked asynchronously!
- * @param {function(boolean, Object|undefined)} callback
- **/
- (function(callback){
- try {
- var audio = new Audio();
- // Shortcut which doesn't work in Chrome (always returns ""); pass through
- // if "maybe" to do asynchronous check by loading MP3 data: URI
- if(audio.canPlayType('audio/mpeg') == "probably")
- callback(true);
-
- // If this event fires, then MP3s can be played
- audio.addEventListener('canplaythrough', function(e) {
- callback(true);
- }, false);
-
- // If this is fired, then client can't play MP3s
- audio.addEventListener('error', function(e){
- callback(false, this.error)
- }, false);
-
- // Smallest base64-encoded MP3 I could come up with (<0.000001 seconds long)
- audio.src = "data:audio/mpeg;base64,/+MYxAAAAANIAAAAAExBTUUzLjk4LjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- audio.load();
- } catch(e) {
- callback(false, e);
+
+ if (document.body) {
+ console.log("'document.body' exists! Script inserted dynamically or outside <head>.");
+ init();
+ } else {
+ console.log("'document.body' not ready, we'll wait for DOMContentLoaded!");
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', init, false);
+ }
+ (function() {
+ /*@cc_on
+ try {
+ document.body.doScroll('up');
+ return init();
+ } catch(e) {}
+ /*@if (false) @*/
+ if (/loaded|complete/.test(document.readyState)) return init();
+ /*@end @*/
+ if (!init.done) setTimeout(arguments.callee, 30);
+ })();
+ if (w.addEventListener) {
+ w.addEventListener('load', init, false);
+ } else if (w.attachEvent) {
+ w.attachEvent('onload', init);
}
- })(nativeCheckComplete);
-})();
+ }
-HTMLMediaElement.htcPath = "HTMLMediaElement.htc";
-HTMLAudioElement.swfPath = "HtmlAudio.swf";
-//HTMLVideoElement.swfPath = "HtmlVideo.swf";
+})(window);
+HTMLMediaElement.setPath("");
View
35 HtmlVideo.htc
@@ -0,0 +1,35 @@
+/* The MIT License
+ *
+ * Copyright (c) 2010 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+<PUBLIC:COMPONENT NAME="HTMLVideoElement" LIGHTWEIGHT=true>
+
+ <PUBLIC:PROPERTY NAME="width" GET="widthGet" PUT="widthSet" />
+ <PUBLIC:PROPERTY NAME="height" GET="heightGet" PUT="heightSet" />
+ <PUBLIC:PROPERTY NAME="videoWidth" GET="videoWidthGet" />
+ <PUBLIC:PROPERTY NAME="videoHeight" GET="videoHeightGet" />
+ <PUBLIC:PROPERTY NAME="poster" GET="posterGet" PUT="posterSet" />
+
+ <SCRIPT LANGUAGE="JScript" >
+
+ </SCRIPT>
+
+</PUBLIC:COMPONENT>
View
BIN  HtmlVideo.swf
Binary file not shown
View
65 README.md
@@ -19,19 +19,29 @@ support via Flash, enhancing the browser's current implementation if one exists.
The `HtmlMedia` library should:
- * If no HTML5 media support is detected, implement the interfaces related to
- the &lt;audio&gt; and &lt;video&gt; element (HTMLMediaElement, HTMLAudioElement,
- etc.) via standard JavaScript, and provide playback support via the Flash
- fallback.
-
- * If some level of HTML5 media is supported, but MP3 playback is not
- (Firefox 3.5+, Opera 10.1), then `HtmlMedia` should augment the native
- implementation to support the codecs provided from the Flash fallback
- *transparently*.
-
- * If HTML5 media is supported AND all the codecs that the fallback supports
+ * If no HTML5 media support is detected (IE8 and below, old versions of
+ Firefox, Safari, etc.), implement the interfaces related to the `<audio>`
+ and `<video>` element (HTMLMediaElement, HTMLAudioElement, etc.) via
+ standard JavaScript, and provide playback support via the Flash fallback.
+ Any Flash-related aspects should be abstracted away and it should
+ *seem* as if the browser vendor decided to implement this part of the HTML5
+ spec themselves!
+
+ * If some level of HTML5 media is supported, but not all codecs supported by
+ the fallback are supported natively, then `HtmlMedia` should augment the
+ native implementation to fallback to Flash when required *completely transparently*.
+
+ * If HTML5 media is supported AND *all* the codecs that the fallback supports
are also supported by the browser (not likely, consider `flv` video files),
then this library should detect so and do nothing.
+
+ * When the Flash fallback must be used, it should integrate with the
+ browser's native event firing model. In other words, the web developer
+ still needs to determine whether to call `attachEvent` or
+ `addEventListener` depending on the browser. This gives a better sense of
+ nativity, and also ensures that it will work properly with other
+ JavaScript compatibility libraries (i.e.
+ [Prototype's Event.observe](http://api.prototypejs.org/dom/event/observe/)).
Ultimately, this gives the web developer access to the HTML5 Audio and Video
API on all browsers with Flash installed (~98%), with **AT LEAST** the codecs
@@ -46,6 +56,7 @@ to drool about:
* Internet Explorer 6+
* Mozilla Firefox 2+
+ * Opera 10.1+ (Possibly 10.0, definitely not 9 or below; no getter/setter support)
* Testing for more needed!!
Supported Codecs
@@ -77,10 +88,10 @@ with guaranteed HTML5 media support via `HtmlMedia`:
You essentially need to copy the `HtmlAudio.swf`, `HtmlVideo.swf`,
`HTMLMediaElement.htc`, `swfobject.js` and the `HtmlMedia.js` files to
-somewhere on your web server. On any page that you want to use HTML5 audio
-or video, just include the `HtmlMedia.js` script which will do the rest.
-Optionally you can set the path to the SWF files. `HtmlMedia` does
-not directly depend on any external libraries, but it is designed to work alongside
+somewhere on your web server. On any page that you want to use HTML5 audio or
+video, just include the `HtmlMedia.js` script which will do the rest.
+Optionally you can set the path to the SWF files. `HtmlMedia` does not
+directly depend on any external libraries, but it is designed to work alongside
popular JavaScript frameworks like [Prototype](http://www.prototypejs.org).
Once the `HtmlMedia.js` file finishes loading you have access to the entire
@@ -109,23 +120,22 @@ old/non-supporting browsers!
If you would like some more tutorials on how to use the HTML5 Audio and Video
API, I highly recommend reading any of the browser vendors' articles on the matter:
-* [Mozilla's "Using audio and video (in Firefox)"](https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox)
+* [Mozilla's "Using audio and video in Firefox"](https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox)
* [Opera's "Everything you need to know about HTML5 video and audio"](http://my.opera.com/core/blog/2010/03/03/everything-you-need-to-know-about-html5-video-and-audio-2)
* [Apple's "Safari Guide to HTML5 Audio and Video"](http://developer.apple.com/safari/library/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Introduction/Introduction.html)
* Is there a Google one anywhere?
-
Known Limitations
-----------------
I'm proud to say that the limitations brought on by this library are very
-minimal, and in most cases, you wouldn't even know about them:
+minimal, and in any regular-use case, you likely wouldn't even know about them:
* innerHTML - DO NOT create `<audio>`, `<video>` or `<source>`
nodes via setting a parent element's `innerHTML` property. The newly created
- nodes are will not be properly extended with the HTML5 API and fallback if
- they are created this way. You can insert said nodes either via
- the initial HTML source, or dynamically via `document.createElement`.
+ nodes will not be properly extended with the HTML5 API and fallback if you
+ do! You can insert said nodes either via the initial HTML source, or
+ dynamically via `document.createElement`.
* It's not possible to overwrite the functionality of the built-in browser
`controls` for media elements. Furthermore, `HtmlMedia` has no plans on
@@ -134,12 +144,13 @@ minimal, and in most cases, you wouldn't even know about them:
user interface do fire, and that is always recommended!
* Currently, upgrading native `<audio>` and `<video>` nodes to use
- the Flash fallback is a destructive process. In other words, if `HTMLMediaElement#load`
- is called and `src` is a file where the Flash fallback needs to be used, then
- that media element is "converted" to a "fallback node" and can only be used to
- play files supported by the fallback, and native playback is removed from that
- element. Just don't be reusing media elements if you're jumping between multiple
- file formats constantly is all, create new ones.
+ the Flash fallback is a destructive process. In other words, if an `error`
+ event is fired, and `currentSrc` is a file where the Flash fallback needs
+ to be used, then that media element is "converted" to a "fallback node" and
+ from then on can only be used to play files supported by the fallback.
+ Native playback is removed from that individual element. Just don't be
+ reusing media elements if you're jumping between multiple file formats
+ constantly is all, create new ones.
License
-------
View
2  build.hxml → build-audio.hxml
@@ -1,4 +1,4 @@
-cp src
--main HtmlAudio
+-main HTMLAudioElement
-swf-version 10
-swf9 HtmlAudio.swf
View
4 build-video.hxml
@@ -0,0 +1,4 @@
+-cp src
+-main HTMLVideoElement
+-swf-version 10
+-swf9 HtmlVideo.swf
View
143 src/HTMLAudioElement.hx
@@ -27,19 +27,12 @@ import flash.media.SoundTransform;
import flash.net.URLRequest;
import haxe.Timer;
-class HTMLAudioElement {
- private var fallbackId: Int;
+class HTMLAudioElement extends HTMLMediaElement {
private var sound : Sound;
private var channel : SoundChannel;
- private var volume : Float;
- private var lastPosition : Float;
- private var lastProgressEvent : Float;
- private var playTimer : Timer;
-
- public function new(fallbackId:Int, src:String, volume:Float) {
- this.fallbackId = fallbackId;
- this.volume = volume;
- this.lastPosition = this.lastProgressEvent = 0;
+
+ public function new(fallbackId:Int, src:String, volume:Float, muted:Bool) {
+ super(fallbackId, src, volume, muted);
this.sound = new Sound();
this.sound.addEventListener("complete", soundComplete);
this.sound.addEventListener("id3", soundId3);
@@ -49,10 +42,10 @@ class HTMLAudioElement {
this.load(src);
}
- public function setVolume(vol: Float) {
- this.volume = vol;
+ public override function setVolume(vol: Float) {
+ super.setVolume(vol);
if (this.channel != null) {
- this.channel.soundTransform = new SoundTransform(vol, 0);
+ this.channel.soundTransform = this.transform;
}
}
@@ -61,11 +54,12 @@ class HTMLAudioElement {
this.pause();
if(this.sound.bytesLoaded < this.sound.bytesTotal)
this.sound.close();
- this.sound.load(new URLRequest(src));
+ this.sound.load(new URLRequest(src));
+ this.metadataSent = false;
}
public function play() {
- this.channel = this.sound.play(this.lastPosition, 0, new SoundTransform(this.volume, 0));
+ this.channel = this.sound.play(this.lastPosition, 0, this.transform);
this.channel.addEventListener("soundComplete", this.channelComplete);
this.playTimer = new Timer(250);
this.playTimer.run = this.sendTimeUpdate;
@@ -98,21 +92,39 @@ class HTMLAudioElement {
this.lastPosition = time*1000;
if (isPlaying)
this.play();
+
+ if (this.lastPosition > this.sound.length) {
+ ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "waiting");
+ } else {
+ ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "seeked");
+ }
}
+ // Called when the sound finishes playing to the end (by SoundChannel's 'soundComplete' event)
private function channelComplete(e) {
- //this.channel.removeEventListener("soundComplete", this.channelComplete);
- //this.channel = null;
- this.pause();
+ this.playTimer.stop();
+ this.channel.removeEventListener("soundComplete", this.channelComplete);
+ this.channel.stop();
+ this.lastPosition = this.channel.position;
ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__endedCallback");
}
/////////////////// Event Handlers ///////////////////
private function soundComplete(e) {
- ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "progress", this.sound.bytesLoaded, this.sound.bytesTotal);
+ ExternalInterface.call("(function() { " +
+ "var s = HTMLAudioElement.__swfSounds["+this.fallbackId+"]; " +
+ "s.__duration = " + (this.sound.length/1000) + "; " +
+ "s.__fireMediaEvent('durationchange'); "+
+ "s.__fireMediaEvent('progress', "+this.sound.bytesLoaded+", "+this.sound.bytesTotal+"); "+
+ "})");
+ //ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "progress", this.sound.bytesLoaded, this.sound.bytesTotal);
}
private function soundId3(e) {
+ //ExternalInterface.call("console.log", e);
+ //if (this.sound.id3["TLEN"]) {
+ //var length : Float = Std.parseFloat(this.sound.id3.TLEN);
+ //}
}
private function soundIoError(e) {
@@ -125,9 +137,94 @@ class HTMLAudioElement {
private function soundProgress(e) {
var now : Float = Date.now().getTime();
- if (now - this.lastProgressEvent > 350) {
- ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "progress", this.sound.bytesLoaded, this.sound.bytesTotal);
+ if (!this.metadataSent) {
+ var percent : Float = this.sound.bytesLoaded / this.sound.bytesTotal;
+ // Set the duration to a calculated estimate while its loading
+ this.duration = this.sound.length * this.sound.bytesLoaded / this.sound.bytesTotal / 1000;
+ if (this.duration > 0 && percent > .05) {
+ ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__metadataCallback", this.duration);
+ this.metadataSent = true;
+ }
+ }
+ if (this.sound.bytesLoaded > 0 && now - this.lastProgressEvent > 350) {
this.lastProgressEvent = now;
+ ExternalInterface.call("HTMLAudioElement.__swfSounds["+this.fallbackId+"].__fireMediaEvent", "progress", this.sound.bytesLoaded, this.sound.bytesTotal);
}
}
-}
+
+
+
+
+
+
+
+ // The static array of Flash "HTMLAudioElement" objects that represent
+ // the <audio> nodes on the page.
+ public static var sounds:Array<HTMLAudioElement> = new Array();
+
+ // ExternalInterface functions available to JavaScript
+ public static function IS_AUDIO_BRIDGE() {
+ return true;
+ }
+
+ public static function createSound(src:String, volume:Float, muted:Bool) {
+ var fallbackId : Int = sounds.length;
+ var a : HTMLAudioElement = new HTMLAudioElement(fallbackId, src, volume, muted);
+ sounds.push(a);
+ return fallbackId;
+ }
+
+ public static function Load(index:Int, src:String) {
+ var sound:HTMLAudioElement = sounds[index];
+ sound.load(src);
+ }
+
+ public static function Play(index:Int) {
+ var sound:HTMLAudioElement = sounds[index];
+ sound.play();
+ }
+
+ public static function Pause(index:Int) {
+ var sound:HTMLAudioElement = sounds[index];
+ sound.pause();
+ }
+
+ public static function SetMuted(index:Int, muted:Bool) {
+ var sound:HTMLAudioElement = sounds[index];
+ sound.setMuted(muted);
+ }
+
+ public static function SetVolume(index:Int, volume:Float) {
+ var sound:HTMLAudioElement = sounds[index];
+ sound.setVolume(volume);
+ }
+
+ public static function GetCurrentTime(index:Int) {
+ return sounds[index].getCurrentTime();
+ }
+
+ public static function SetCurrentTime(index:Int, time:Float) {
+ sounds[index].setCurrentTime(time);
+ }
+
+ public static function main() {
+ ExternalInterface.addCallback("IS_AUDIO_BRIDGE", IS_AUDIO_BRIDGE);
+ ExternalInterface.addCallback("__createSound", createSound);
+ ExternalInterface.addCallback("__load", Load);
+ ExternalInterface.addCallback("__play", Play);
+ ExternalInterface.addCallback("__pause", Pause);
+ ExternalInterface.addCallback("__setMuted", SetMuted);
+ ExternalInterface.addCallback("__setVolume", SetVolume);
+ ExternalInterface.addCallback("__getCurrentTime", GetCurrentTime);
+ ExternalInterface.addCallback("__setCurrentTime", SetCurrentTime);
+ ExternalInterface.call([
+ "(function(){",
+ "var f = function(tag){",
+ "var elems = document.getElementsByTagName(tag);",
+ "for (var i=0; i<elems.length; i++) if (elems[i].IS_AUDIO_BRIDGE) return elems[i];",
+ "};",
+ "HTMLAudioElement.__swf = f('embed') || f('object');",
+ "})" ].join('') );
+ ExternalInterface.call("HTMLAudioElement.__swfLoaded");
+ }
+}
View
56 src/HTMLMediaElement.hx
@@ -0,0 +1,56 @@
+/* The MIT License
+ *
+ * Copyright (c) 2010 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+import flash.media.SoundTransform;
+import haxe.Timer;
+
+class HTMLMediaElement {
+ var fallbackId : Int;
+ var transform : SoundTransform;
+ var volume : Float;
+ var muted : Bool;
+ var lastPosition : Float;
+ var duration : Float;
+ var isDurationPositive : Bool;
+ var lastProgressEvent : Float;
+ var playTimer : Timer;
+ var metadataSent : Bool;
+
+ public function new(fallbackId:Int, src:String, volume:Float, muted:Bool) {
+ this.fallbackId = fallbackId;
+ this.volume = volume;
+ this.muted = muted;
+ this.lastPosition = this.lastProgressEvent = 0;
+ this.isDurationPositive = false;
+ this.transform = new SoundTransform(muted ? 0 : volume, 0);
+ }
+
+ public function setMuted(muted: Bool) {
+ this.muted = muted;
+ this.setVolume(this.volume);
+ }
+
+ public function setVolume(vol: Float) {
+ this.volume = vol;
+ this.transform.volume = this.muted ? 0 : vol;
+ }
+}
View
63 src/HTMLVideoElement.hx
@@ -1,7 +1,62 @@
-class HTMLVideoElement {
- private var fallbackId: Int;
+/* The MIT License
+ *
+ * Copyright (c) 2010 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+import flash.external.ExternalInterface;
+import flash.net.NetStream;
+import flash.net.NetConnection;
+import flash.media.Video;
+
+class HTMLVideoElement extends HTMLMediaElement {
+ private var stream : NetStream;
+ private var connection : NetConnection;
+ private var video: Video;
- public function new(fallbackId:Int, src:String, volume:Float, ?width:Int = null, ?height:Int = null) {
+ public function new(fallbackId:Int, src:String, volume:Float, muted:Bool) {
+ super(fallbackId, src, volume, muted);
+ this.connection = new NetConnection();
+ this.connection.connect(null);
+ this.stream = new NetStream(this.connection);
+ this.stream.client = this;
+ this.stream.soundTransform = this.transform;
+ this.video = new Video();
+ this.video.attachNetStream(this.stream);
}
-}
+
+ public override function setVolume(vol: Float) {
+ super.setVolume(vol);
+ }
+
+ public function getVid() {
+ return this.video;
+ }
+
+ private function onMetaData(metadata) {
+
+ }
+
+ public static function main() {
+ var flashvars : Dynamic<String> = flash.Lib.current.loaderInfo.parameters;
+ var video : HTMLVideoElement = new HTMLVideoElement(Std.parseInt(flashvars.id), flashvars.src, Std.parseFloat(flashvars.volume), flashvars.muted.toLowerCase() == "true");
+ flash.Lib.current.addChild(video.getVid());
+ }
+}
View
103 src/HtmlAudio.hx
@@ -1,103 +0,0 @@
-/* The MIT License
- *
- * Copyright (c) 2010 Nathan Rajlich
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-import flash.events.Event;
-import flash.events.IOErrorEvent;
-import flash.events.SecurityErrorEvent;
-import flash.events.ProgressEvent;
-import flash.external.ExternalInterface;
-
-class HtmlAudio {
- public static var sounds:Array<HTMLAudioElement> = new Array();
-
- public static function IS_AUDIO_BRIDGE() {
- return true;
- }
-
- /* Called from JavaScript; HTMLAudioElement#load.
- * Should create a new HTMLAudioElement AS object, and begin loading
- * the specified resource. Creating an HTMLAudioElement in Flash is
- * deferred until HTMLAudioElement#load is called by the webpage.
- *
- * @return The array index of the new HTMLAudioElement, which should be
- * matched in the HTMLAudioElement.__swfSounds Array in JavaScript.
- */
- public static function createSound(src:String, volume:Float) {
- var fallbackId : Int = sounds.length;
- var a : HTMLAudioElement = new HTMLAudioElement(fallbackId, src, volume);
- sounds.push(a);
- return fallbackId; // Return the index of the new Sound in the array
- }
-
- // ExternalInterface functions available to JavaScript
- public static function load(index:Int, src:String) {
- var sound:HTMLAudioElement = sounds[index];
- sound.load(src);
- }
-
- public static function play(index:Int) {
- var sound:HTMLAudioElement = sounds[index];
- sound.play();
- }
-
- public static function pause(index:Int) {
- var sound:HTMLAudioElement = sounds[index];
- sound.pause();
- }
-
- public static function setVolume(index:Int, volume:Float) {
- var sound:HTMLAudioElement = sounds[index];
- sound.setVolume(volume);
- }
-
- public static function getCurrentTime(index:Int) {
- return sounds[index].getCurrentTime();
- }
-
- public static function setCurrentTime(index:Int, time:Float) {
- sounds[index].setCurrentTime(time);
- }
-
-
-
-
-
- public static function main() {
- ExternalInterface.addCallback("IS_AUDIO_BRIDGE", IS_AUDIO_BRIDGE);
- ExternalInterface.addCallback("__createSound", createSound);
- ExternalInterface.addCallback("__load", load);
- ExternalInterface.addCallback("__play", play);
- ExternalInterface.addCallback("__pause", pause);
- ExternalInterface.addCallback("__setVolume", setVolume);
- ExternalInterface.addCallback("__getCurrentTime", getCurrentTime);
- ExternalInterface.addCallback("__setCurrentTime", setCurrentTime);
- ExternalInterface.call([
- "(function(){",
- "var f = function(tag){",
- "var elems = document.getElementsByTagName(tag);",
- "for (var i=0; i<elems.length; i++) if (elems[i].IS_AUDIO_BRIDGE) return elems[i];",
- "};",
- "HTMLAudioElement.__swf = f('embed') || f('object');",
- "})" ].join('') );
- ExternalInterface.call("HTMLAudioElement.__swfLoaded");
- }
-}
View
30 tests/audio.html
@@ -11,9 +11,9 @@
<script type="text/javascript" src="../swfobject.js"></script>
<script type="text/javascript" src="../HtmlMedia.js"></script>
<script type="text/javascript">
- HTMLMediaElement.htcPath = "../HTMLMediaElement.htc";
- HTMLAudioElement.swfPath = "../HtmlAudio.swf";
- //HTMLVideoElement.swfPath = "../HtmlVideo.swf";
+ // The only optional configuration is to set the
+ // path to the location of the dependant files.
+ HTMLMediaElement.setPath("..");
document.observe("dom:loaded", function() {
ignore = false;
@@ -61,7 +61,9 @@
function logEvent(ev) {
var textArea = $("event-log");
- textArea.value += '\n' + (new Date).getTime() + ': "' + ev.type + '" event fired';
+ var val = '\n' + (new Date).getTime() + ': "' + ev.type + '" event fired';
+ //console.log(val);
+ textArea.value += val;
textArea.scrollTop = textArea.scrollHeight;
}
@@ -69,14 +71,16 @@
"stalled", "play", "pause", "loadedmetadata", "loadeddata",
"waiting", "playing", "canplay", "canplaythrough", "seeking",
"seeked", "timeupdate", "ended", "ratechange", "durationchange",
- "volumechange"].each(function(e) {
- a.observe(e, logEvent);
+ "volumechange", "fallback"].each(function(eventName) {
+ a.observe(eventName, logEvent);
});
a.observe("timeupdate", function() {
var t = (new Date).getTime();
if (!isNaN(lastTimeUpdate)) {
var textArea = $("event-log");
- textArea.value += " ("+(t-lastTimeUpdate)+")";
+ var val = " ("+(t-lastTimeUpdate)+")";
+ //console.log(val);
+ textArea.value += val;
textArea.scrollTop = textArea.scrollHeight;
}
lastTimeUpdate = t;
@@ -89,12 +93,12 @@
<!-- So as you can see, we're just using standards compliant HTML5 markup
to create the <audio> node. By including 'HtmlMedia.js' in the <head>,
we can be confident that it will work in ~98% of browsers today! -->
- <audio src="sounds/ping.mp3" loop="loop"
+ <audio src="sounds/ping.mp3" loop="loop" autoplay=1
ondurationchange="$('duration').update(this.duration)"
onvolumechange="$('volume').update(this.volume + (this.muted ? ', muted' : ''))"
- onended="console.log('song ended');"
- onprogress="$('loaded').update(event.lengthComputable ? event.loaded/event.total * 100 + '%' : this.buffered.end(0)/this.duration * 100 + '%*');"
- ontimeupdate="if (timeSlider && this.duration) { ignore = true; timeSlider.setValue(this.currentTime / this.duration); ignore = false; } $('currentTime').update(this.currentTime)">
+ onprogress="$('loaded').update(event.lengthComputable ? event.loaded/event.total * 100 + '%' : this.buffered.end(0)/this.duration * 100 + '%*')"
+ ontimeupdate="if (timeSlider && this.duration) { ignore = true; timeSlider.setValue(this.currentTime / this.duration); ignore = false; } $('currentTime').update(this.currentTime)"
+ onfallback="$('isNative').update(this.isNative)">
</audio>
<button id="load" onclick="$$('audio')[0].load()">Load</button>
@@ -141,7 +145,7 @@
</tr>
</table>
- <textarea id="event-log" style="width:90%; height:200px;"></textarea>
-
+ <textarea id="event-log" style="width:90%; height:200px;"></textarea><br />
+ <button onclick="$('event-log').value = '';">Clear "Event Log"</button>
</body>
</html>
View
8 tests/video.html
@@ -1,18 +1,16 @@
<!DOCTYPE html>
<html>
<head>
- <title>HTML5 &lt;video&gt; Flash Fallback</title>
+ <title>HTML5 &lt;video&gt; Flash Fallback</title>
<script type="text/javascript" src="../swfobject.js"></script>
<script type="text/javascript" src="../HtmlMedia.js"></script>
<script type="text/javascript">
- HTMLMediaElement.htcPath = "../HTMLMediaElement.htc";
- HTMLAudioElement.swfPath = "../HtmlAudio.swf";
- //HTMLVideoElement.swfPath = "../HtmlVideo.swf";
+ HTMLMediaElement.setPath("../");
</script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js"></script>
</head>
<body>
- <video src="videos/20051210-w50s.flv"></video>
+ <video src="videos/race.flv"></video>
</body>
</html>
Please sign in to comment.
Something went wrong with that request. Please try again.