/** * @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 */ import Tech from './tech'; import * as Lib from '../lib'; import FlashRtmpDecorator from './flash-rtmp'; import Component from '../component'; import window from 'global/window'; let navigator = window.navigator; /** * Flash Media Controller - Wrapper for fallback SWF API * * @param {Player} player * @param {Object=} options * @param {Function=} ready * @constructor */ class Flash extends Tech { constructor(options, ready){ super(options, ready); let { source, parentEl } = options; // Generate ID for swf object let objId = options.playerId+'_flash_api'; // Merge default flashvars with ones passed in to init let flashVars = Lib.obj.merge({ // SWF Callback Functions 'readyFunction': 'videojs.Flash.onReady', 'eventProxyFunction': 'videojs.Flash.onEvent', 'errorEventProxyFunction': 'videojs.Flash.onError', // Player Settings 'autoplay': options.autoplay, 'preload': options.preload, 'loop': options.loop, 'muted': options.muted }, options.flashVars); // Merge default parames with ones passed in let params = Lib.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 let attributes = Lib.obj.merge({ 'id': objId, 'name': objId, // Both ID and Name needed or swf to identify itself 'class': 'vjs-tech' }, options.attributes); // If source was supplied pass as a flash var. if (source) { this.ready(function(){ this.setSource(source); }); } // 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); }); } window.videojs = window.videojs || {}; window.videojs.Flash = window.videojs.Flash || {}; window.videojs.Flash.onReady = Flash.onReady; window.videojs.Flash.onEvent = Flash.onEvent; window.videojs.Flash.onError = Flash.onError; this.el_ = Flash.embed(options.swf, flashVars, params, attributes); this.el_.tech = this; } play() { this.el_.vjs_play(); } pause() { this.el_.vjs_pause(); } src(src) { if (src === undefined) { return this.currentSrc(); } // Setting src through `src` not `setSrc` will be deprecated return this.setSrc(src); } setSrc(src) { // Make sure source URL is absolute. src = Lib.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.autoplay()) { var tech = this; this.setTimeout(function(){ tech.play(); }, 0); } } setCurrentTime(time) { this.lastSeekTarget_ = time; this.el_.vjs_setProperty('currentTime', time); super.setCurrentTime(); } currentTime(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'); } currentSrc() { if (this.currentSource_) { return this.currentSource_.src; } else { return this.el_.vjs_getProperty('currentSrc'); } } load() { this.el_.vjs_load(); } poster() { this.el_.vjs_getProperty('poster'); } // poster images are not handled by the Flash tech so make this a no-op setPoster() {} buffered() { return Lib.createTimeRange(0, this.el_.vjs_getProperty('buffered')); } supportsFullScreen() { return false; // Flash does not allow fullscreen through javascript } enterFullScreen() { return false; } } // Create setters and getters for attributes const _api = Flash.prototype; const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','); const _readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(','); function _createSetter(attr){ var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); _api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); }; } function _createGetter(attr) { _api[attr] = function(){ return this.el_.vjs_getProperty(attr); }; } // Create getter and setters for all read/write attributes for (let i = 0; i < _readWrite.length; i++) { _createGetter(_readWrite[i]); _createSetter(_readWrite[i]); } // Create getters for read-only attributes for (let i = 0; i < _readOnly.length; i++) { _createGetter(_readOnly[i]); } /* Flash Support Testing -------------------------------------------------------- */ Flash.isSupported = function(){ return Flash.version()[0] >= 10; // return swfobject.hasFlashPlayerVersion('10'); }; // Add Source Handler pattern functions to this tech Tech.withSourceHandlers(Flash); /** * The default native source handler. * This simply passes the source to the video element. Nothing fancy. * @param {Object} source The source object * @param {Flash} tech The instance of the Flash tech */ Flash.nativeSourceHandler = {}; /** * Check Flash can handle the source natively * @param {Object} source The source object * @return {String} 'probably', 'maybe', or '' (empty string) */ Flash.nativeSourceHandler.canHandleSource = function(source){ var type; function guessMimeType(src) { var ext = Lib.getFileExtension(src); if (ext) { return `video/${ext}`; } return ''; } if (!source.type) { type = guessMimeType(source.src); } else { // Strip code information from the type because we don't get that specific type = source.type.replace(/;.*/, '').toLowerCase(); } if (type in Flash.formats) { return 'maybe'; } return ''; }; /** * Pass the source to the flash object * Adaptive source handlers will have more complicated workflows before passing * video data to the video element * @param {Object} source The source object * @param {Flash} tech The instance of the Flash tech */ Flash.nativeSourceHandler.handleSource = function(source, tech){ tech.setSrc(source.src); }; /** * Clean up the source handler when disposing the player or switching sources.. * (no cleanup is needed when supporting the format natively) */ Flash.nativeSourceHandler.dispose = function(){}; // Register the native source handler Flash.registerSourceHandler(Flash.nativeSourceHandler); Flash.formats = { 'video/flv': 'FLV', 'video/x-flv': 'FLV', 'video/mp4': 'MP4', 'video/m4v': 'MP4' }; Flash.onReady = function(currSwf){ let el = Lib.el(currSwf); let tech = el && el.tech; // if there is no el then the tech has been disposed // and the tech element was removed from the player div if (tech && tech.el()) { // check that the flash object is really ready Flash.checkReady(tech); } }; // The SWF isn't always 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. 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) { // tell tech it's ready tech.triggerReady(); } else { // wait longer this.setTimeout(function(){ Flash['checkReady'](tech); }, 50); } }; // Trigger events from the swf on the player Flash.onEvent = function(swfID, eventName){ let tech = Lib.el(swfID).tech; tech.trigger(eventName); }; // Log errors from the swf Flash.onError = function(swfID, err){ const tech = Lib.el(swfID).tech; const msg = 'FLASH: '+err; if (err === 'srcnotfound') { tech.trigger('error', { code: 4, message: msg }); // errors we haven't categorized into the media errors } else { tech.trigger('error', msg); } }; // Flash Version Check Flash.version = function(){ let 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 Flash.embed = function(swf, flashVars, params, attributes){ const code = Flash.getEmbedCode(swf, flashVars, params, attributes); // Get element by embedding code and retrieving created element const obj = Lib.createEl('div', { innerHTML: code }).childNodes[0]; return obj; }; Flash.getEmbedCode = function(swf, flashVars, params, attributes){ const objTag = '`; }); attributes = Lib.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 Lib.obj.each(attributes, function(key, val){ attrsString += `${key}="${val}" `; }); return `${objTag}${attrsString}>${paramsString}`; }; // Run Flash through the RTMP decorator FlashRtmpDecorator(Flash); Tech.registerComponent('Flash', Flash); export default Flash;