/** * @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); 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; 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){ // Stop worrying if the tech has been disposed if (!tech.el()) { return; } // 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']; 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 = ''; }); 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 + ''; }; 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); };