1
0
mirror of https://github.com/videojs/video.js.git synced 2025-04-21 12:17:11 +02:00
video.js/src/js/media/flash.js

583 lines
20 KiB
JavaScript
Raw Normal View History

/**
* @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
* https://github.com/zencoder/video-js-swf
* Not using setupTriggers. Using global onEvent func to distribute events
*/
/**
* Flash Media Controller - Wrapper for fallback SWF API
*
* @param {vjs.Player} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
vjs.Flash = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
vjs.MediaTechController.call(this, player, options, ready);
var source = options['source'],
// Which element to embed in
parentEl = options['parentEl'],
// Create a temporary element to be replaced by swf object
placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
// Generate ID for swf object
objId = player.id()+'_flash_api',
// Store player options in local var for optimization
// TODO: switch to using player methods instead of options
// e.g. player.autoplay();
playerOptions = player.options_,
// Merge default flashvars with ones passed in to init
flashVars = vjs.obj.merge({
// SWF Callback Functions
'readyFunction': 'videojs.Flash.onReady',
'eventProxyFunction': 'videojs.Flash.onEvent',
'errorEventProxyFunction': 'videojs.Flash.onError',
// Player Settings
'autoplay': playerOptions.autoplay,
'preload': playerOptions.preload,
'loop': playerOptions.loop,
'muted': playerOptions.muted
}, options['flashVars']),
// Merge default parames with ones passed in
params = vjs.obj.merge({
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
}, options['params']),
// Merge default attributes with ones passed in
attributes = vjs.obj.merge({
'id': objId,
'name': objId, // Both ID and Name needed or swf to identifty itself
'class': 'vjs-tech'
}, options['attributes'])
;
// If source was supplied pass as a flash var.
if (source) {
if (source.type && vjs.Flash.isStreamingType(source.type)) {
var parts = vjs.Flash.streamToParts(source.src);
flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
}
else {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
}
// Add placeholder to player div
vjs.insertFirst(placeHolder, parentEl);
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
// This allows resetting the playhead when we catch the reload
if (options['startTime']) {
this.ready(function(){
this.load();
this.play();
this.currentTime(options['startTime']);
});
}
// firefox doesn't bubble mousemove events to parent. videojs/video-js-swf#37
// bugzilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=836786
if (vjs.IS_FIREFOX) {
this.ready(function(){
vjs.on(this.el(), 'mousemove', vjs.bind(this, function(){
// since it's a custom event, don't bubble higher than the player
this.player().trigger({ 'type':'mousemove', 'bubbles': false });
}));
});
}
// native click events on the SWF aren't triggered on IE11, Win8.1RT
// use stageclick events triggered from inside the SWF instead
player.on('stageclick', player.reportUserActivity);
// Flash iFrame Mode
// In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
// - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
// - Webkit when hiding the plugin
// - Webkit and Firefox when using requestFullScreen on a parent element
// Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
// Issues that remain include hiding the element and requestFullScreen in Firefox specifically
// There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
// Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
// I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
// Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
// In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
// The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
// NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
// Firefox 9 throws a security error, unleess you call location.href right before doc.write.
// Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
// Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
// Create iFrame with vjs-tech class so it's 100% width/height
var iFrm = vjs.createEl('iframe', {
'id': objId + '_iframe',
'name': objId + '_iframe',
'className': 'vjs-tech',
'scrolling': 'no',
'marginWidth': 0,
'marginHeight': 0,
'frameBorder': 0
});
// Update ready function names in flash vars for iframe window
flashVars['readyFunction'] = 'ready';
flashVars['eventProxyFunction'] = 'events';
flashVars['errorEventProxyFunction'] = 'errors';
// Tried multiple methods to get this to work in all browsers
// Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
// The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
// var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
// (in onload)
// var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
// iDoc.body.appendChild(temp);
// Tried embedding the flash object through javascript in the iframe source.
// This works in webkit but still triggers the firefox security error
// iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
// Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
// We should add an option to host the iframe locally though, because it could help a lot of issues.
// iFrm.src = "iframe.html";
// Wait until iFrame has loaded to write into it.
vjs.on(iFrm, 'load', vjs.bind(this, function(){
var iDoc,
iWin = iFrm.contentWindow;
// The one working method I found was to use the iframe's document.write() to create the swf object
// This got around the security issue in all browsers except firefox.
// I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
// However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
// Plus Firefox 3.6 didn't work no matter what I tried.
// if (vjs.USER_AGENT.match('Firefox')) {
// iWin.location.href = '';
// }
// Get the iFrame's document depending on what the browser supports
iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
// Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
// Even tried adding /. that was mentioned in a browser security writeup
// document.domain = document.domain+'/.';
// iDoc.domain = document.domain+'/.';
// Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
// iDoc.body.innerHTML = swfObjectHTML;
// Tried appending the object to the iframe doc's body. Security error in all browsers.
// iDoc.body.appendChild(swfObject);
// Using document.write actually got around the security error that browsers were throwing.
// Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
// Not sure why that's a security issue, but apparently it is.
iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
// Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
// So far no issues with swf ready event being called before it's set on the window.
iWin['player'] = this.player_;
// Create swf ready function for iFrame window
iWin['ready'] = vjs.bind(this.player_, function(currSwf){
var el = iDoc.getElementById(currSwf),
player = this,
tech = player.tech;
// Update reference to playback technology element
tech.el_ = el;
// Make sure swf is actually ready. Sometimes the API isn't actually yet.
vjs.Flash.checkReady(tech);
});
// Create event listener for all swf events
iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
var player = this;
if (player && player.techName === 'flash') {
player.trigger(eventName);
}
});
// Create error listener for all swf errors
iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
vjs.log('Flash Error', eventName);
});
}));
// Replace placeholder with iFrame (it will load now)
placeHolder.parentNode.replaceChild(iFrm, placeHolder);
// If not using iFrame mode, embed as normal object
} else {
vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
}
}
});
vjs.Flash.prototype.dispose = function(){
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Flash.prototype.play = function(){
this.el_.vjs_play();
};
vjs.Flash.prototype.pause = function(){
this.el_.vjs_pause();
};
vjs.Flash.prototype.src = function(src){
if (src === undefined) {
return this.currentSrc();
}
if (vjs.Flash.isStreamingSrc(src)) {
src = vjs.Flash.streamToParts(src);
this.setRtmpConnection(src.connection);
this.setRtmpStream(src.stream);
} else {
// Make sure source URL is abosolute.
src = vjs.getAbsoluteURL(src);
this.el_.vjs_src(src);
}
// Currently the SWF doesn't autoplay if you load a source later.
// e.g. Load player w/ no source, wait 2s, set src.
if (this.player_.autoplay()) {
var tech = this;
setTimeout(function(){ tech.play(); }, 0);
}
};
vjs.Flash.prototype.setCurrentTime = function(time){
this.lastSeekTarget_ = time;
this.el_.vjs_setProperty('currentTime', time);
};
vjs.Flash.prototype.currentTime = function(time){
// when seeking make the reported time keep up with the requested time
// by reading the time we're seeking to
if (this.seeking()) {
return this.lastSeekTarget_ || 0;
}
return this.el_.vjs_getProperty('currentTime');
};
vjs.Flash.prototype.currentSrc = function(){
var src = this.el_.vjs_getProperty('currentSrc');
// no src, check and see if RTMP
if (src == null) {
var connection = this['rtmpConnection'](),
stream = this['rtmpStream']();
if (connection && stream) {
src = vjs.Flash.streamFromParts(connection, stream);
}
}
return src;
};
vjs.Flash.prototype.load = function(){
this.el_.vjs_load();
};
vjs.Flash.prototype.poster = function(){
this.el_.vjs_getProperty('poster');
};
vjs.Flash.prototype.setPoster = function(){
// poster images are not handled by the Flash tech so make this a no-op
};
vjs.Flash.prototype.buffered = function(){
return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
};
vjs.Flash.prototype.supportsFullScreen = function(){
return false; // Flash does not allow fullscreen through javascript
};
vjs.Flash.prototype.enterFullScreen = function(){
return false;
};
// Create setters and getters for attributes
var api = vjs.Flash.prototype,
readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
// Overridden: buffered, currentTime, currentSrc
/**
* @this {*}
* @private
*/
var createSetter = function(attr){
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
};
/**
* @this {*}
* @private
*/
var createGetter = function(attr){
api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
};
(function(){
var i;
// Create getter and setters for all read/write attributes
for (i = 0; i < readWrite.length; i++) {
createGetter(readWrite[i]);
createSetter(readWrite[i]);
}
// Create getters for read-only attributes
for (i = 0; i < readOnly.length; i++) {
createGetter(readOnly[i]);
}
})();
/* Flash Support Testing -------------------------------------------------------- */
vjs.Flash.isSupported = function(){
return vjs.Flash.version()[0] >= 10;
// return swfobject.hasFlashPlayerVersion('10');
};
vjs.Flash.canPlaySource = function(srcObj){
var type;
if (!srcObj.type) {
return '';
}
type = srcObj.type.replace(/;.*/,'').toLowerCase();
if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {
return 'maybe';
}
};
vjs.Flash.formats = {
'video/flv': 'FLV',
'video/x-flv': 'FLV',
'video/mp4': 'MP4',
'video/m4v': 'MP4'
};
vjs.Flash.streamingFormats = {
'rtmp/mp4': 'MP4',
'rtmp/flv': 'FLV'
};
vjs.Flash['onReady'] = function(currSwf){
var el = vjs.el(currSwf);
// Get player from box
// On firefox reloads, el might already have a player
var player = el['player'] || el.parentNode['player'],
tech = player.tech;
// Reference player on tech element
el['player'] = player;
// Update reference to playback technology element
tech.el_ = el;
vjs.Flash.checkReady(tech);
};
// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
// If it's not ready, we set a timeout to check again shortly.
vjs.Flash.checkReady = function(tech){
// Check if API property exists
if (tech.el().vjs_getProperty) {
// If so, tell tech it's ready
tech.triggerReady();
// Otherwise wait longer.
} else {
setTimeout(function(){
vjs.Flash.checkReady(tech);
}, 50);
}
};
// Trigger events from the swf on the player
vjs.Flash['onEvent'] = function(swfID, eventName){
var player = vjs.el(swfID)['player'];
player.trigger(eventName);
};
// Log errors from the swf
vjs.Flash['onError'] = function(swfID, err){
var player = vjs.el(swfID)['player'];
Better error handling. closes #1197 Squashed commit of the following: commit 81d785980d3f3e4c1025f7b421f0ecb7320469f1 Author: Steve Heffernan <steve@zencoder.com> Date: Mon May 12 12:53:59 2014 -0700 Removed unneeded comments commit c7ad7322e49df9cb22665692dbfe615dfa44758f Author: Steve Heffernan <steve@zencoder.com> Date: Fri May 9 14:29:31 2014 -0700 Addressed comments in #1191 Now clearing errors on loadstart events. Added some default error messages. commit a742239d0e799fa6a5fee056cc37b3c2e3ab4510 Author: Steve Heffernan <steve@zencoder.com> Date: Wed May 7 15:38:31 2014 -0700 Fixed the error display to hide by default commit 561c3f844956db6f532cae8ed81a86cc39b10db1 Author: Steve Heffernan <steve@zencoder.com> Date: Mon May 5 10:44:47 2014 -0700 Added support for displaying a message for the error. commit 22142078427ead85548c4755bf1943a0a07b22b4 Author: Steve Heffernan <steve@zencoder.com> Date: Fri May 2 17:18:22 2014 -0700 Updated spinner to hide on all errors commit 95d7e7027467cf96b14db6692d93c7c7f41c5810 Author: Steve Heffernan <steve@zencoder.com> Date: Fri May 2 15:37:44 2014 -0700 Exported ErrorDisplay commit 11ca9cdd8db4d1559f5d1908c4e67be32ca7a25e Author: Steve Heffernan <steve@zencoder.com> Date: Fri May 2 15:35:46 2014 -0700 Updated flash tech to support new errors commit 56cbe66f4233e54f13550367590864102f5de0fe Author: Steve Heffernan <steve@zencoder.com> Date: Fri May 2 13:06:49 2014 -0700 Started on better error handling and displaying in the UI when an error has occurred. commit 740014c57b264079cf4084965a9384b49a7c0f64 Author: Steve Heffernan <steve@zencoder.com> Date: Wed Apr 30 16:11:33 2014 -0700 Added better global log/error/warn functions. Added sinon.js for stubs in tests. Updated grunt version to satisfy peer dependency warning.
2014-05-12 17:08:48 -07:00
var msg = 'FLASH: '+err;
if (err == 'srcnotfound') {
player.error({ code: 4, message: msg });
// errors we haven't categorized into the media errors
} else {
player.error(msg);
}
};
// Flash Version Check
vjs.Flash.version = function(){
var version = '0,0,0';
// IE
try {
version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
// other browsers
} catch(e) {
try {
if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
}
} catch(err) {}
}
return version.split(',');
};
// Flash embedding method. Only used in non-iframe mode
vjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){
var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
// Get element by embedding code and retrieving created element
obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
par = placeHolder.parentNode
;
placeHolder.parentNode.replaceChild(obj, placeHolder);
// IE6 seems to have an issue where it won't initialize the swf object after injecting it.
// This is a dumb fix
var newObj = par.childNodes[0];
setTimeout(function(){
newObj.style.display = 'block';
}, 1000);
return obj;
};
vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
var objTag = '<object type="application/x-shockwave-flash"',
flashVarsString = '',
paramsString = '',
attrsString = '';
// Convert flash vars to string
if (flashVars) {
vjs.obj.each(flashVars, function(key, val){
flashVarsString += (key + '=' + val + '&amp;');
});
}
// Add swf, flashVars, and other default params
params = vjs.obj.merge({
'movie': swf,
'flashvars': flashVarsString,
'allowScriptAccess': 'always', // Required to talk to swf
'allowNetworking': 'all' // All should be default, but having security issues.
}, params);
// Create param tags string
vjs.obj.each(params, function(key, val){
paramsString += '<param name="'+key+'" value="'+val+'" />';
});
attributes = vjs.obj.merge({
// Add swf to attributes (need both for IE and Others to work)
'data': swf,
// Default to 100% width/height
'width': '100%',
'height': '100%'
}, attributes);
// Create Attributes string
vjs.obj.each(attributes, function(key, val){
attrsString += (key + '="' + val + '" ');
});
return objTag + attrsString + '>' + paramsString + '</object>';
};
vjs.Flash.streamFromParts = function(connection, stream) {
return connection + '&' + stream;
};
vjs.Flash.streamToParts = function(src) {
var parts = {
connection: '',
stream: ''
};
if (! src) {
return parts;
}
// Look for the normal URL separator we expect, '&'.
// If found, we split the URL into two pieces around the
// first '&'.
var connEnd = src.indexOf('&');
var streamBegin;
if (connEnd !== -1) {
streamBegin = connEnd + 1;
}
else {
// If there's not a '&', we use the last '/' as the delimiter.
connEnd = streamBegin = src.lastIndexOf('/') + 1;
if (connEnd === 0) {
// really, there's not a '/'?
connEnd = streamBegin = src.length;
}
}
parts.connection = src.substring(0, connEnd);
parts.stream = src.substring(streamBegin, src.length);
return parts;
};
vjs.Flash.isStreamingType = function(srcType) {
return srcType in vjs.Flash.streamingFormats;
};
// RTMP has four variations, any string starting
// with one of these protocols should be valid
vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
vjs.Flash.isStreamingSrc = function(src) {
return vjs.Flash.RTMP_RE.test(src);
};