├── swf
├── H264_Encoder.swf
└── playerProductInstall.swf
├── .gitattributes
├── .gitignore
├── README.md
├── send_h264.php
├── src
└── H264_send.as
└── js
└── h264_swfobject.js
/swf/H264_Encoder.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crsepulv/webcam-flash-rtmp-h264/HEAD/swf/H264_Encoder.swf
--------------------------------------------------------------------------------
/swf/playerProductInstall.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crsepulv/webcam-flash-rtmp-h264/HEAD/swf/playerProductInstall.swf
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webcam-flash-rtmp-h264
2 | in Browser RTMP streaming using the webcam, the encoding is using H264
3 |
4 | Based on this tutotrial: http://www.adobe.com/devnet/adobe-media-server/articles/encoding-live-video-h264.html
5 |
6 | ##Intro
7 |
8 | This code allows you to stream a single webcam (Audio&VIdeo) to a RTMP server like ADOBE, WOWZA, etc.
9 |
10 | ##Install
11 |
12 | ###swf/H264_Encoder.swf
13 | is compiled from src/H264_send.as and is the object that allows the webcam streaming
14 | ###swf/playerProductInstall.swf
15 | in in case the user doesn't have flash installed.
16 |
17 | ###send_h264.php
18 | is where you configure the params and display it on browser.
19 |
20 | actual configuration includes:
21 |
22 | ##Camera:
23 | ( http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Camera.html )
24 |
25 | ###camera_bandwidth:"100000"
26 | Specifies the maximum amount of bandwidth that the current outgoing video feed can use, in bytes per second. To specify that the video can use as much bandwidth as needed to maintain the value of quality, pass 0 for bandwidth. The default value is 16384.
27 |
28 | ###camera_quality:"0"
29 | An integer that specifies the required level of picture quality, as determined by the amount of compression being applied to each video frame. Acceptable values range from 1 (lowest quality, maximum compression) to 100 (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth, pass 0 for quality.
30 |
31 | ###camera_fps:"30"
32 | Frames per Second
33 |
34 | ###camera_KeyFrameInterval:"30"
35 | The number of video frames transmitted in full (called keyframes) instead of being interpolated by the video compression algorithm. The default value is 15, which means that every 15th frame is a keyframe. A value of 1 means that every frame is a keyframe. The allowed values are 1 through 300.
36 |
37 | ###camera_loopback:"0"
38 | Indicates whether a local view of what the camera is capturing is compressed and decompressed (true), as it would be for live transmission using Flash Media Server, or uncompressed (false).
39 |
40 |
41 | ##Microphone
42 | ( http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Microphone.html)
43 |
44 | ###mic_codec:"SoundCodec.SPEEX"
45 | The codec to use for compressing audio. Available codecs are Nellymoser (the default) and Speex. The enumeration class SoundCodec contains the various values that are valid for the codec property.
46 |
47 | If you use the Nellymoser codec, you can set the sample rate using Microphone.rate(). If you use the Speex codec, the sample rate is set to 16 kHz.
48 |
49 | Speex includes voice activity detection (VAD) and automatically reduces bandwidth when no voice is detected. When using the Speex codec, Adobe recommends that you set the silence level to 0. To set the silence level, use the Microphone.setSilenceLevel() method.
50 |
51 | ###mic_encodeQuality:"8"
52 | The encoded speech quality when using the Speex codec. Possible values are from 0 to 10. The default value is 6. Higher numbers represent higher quality but require more bandwidth, as shown in the following table. The bit rate values that are listed represent net bit rates and do not include packetization overhead.
53 |
54 | Quality value Required bit rate (kilobits per second)
55 | 0 3.95
56 | 1 5.75
57 | 2 7.75
58 | 3 9.80
59 | 4 12.8
60 | 5 16.8
61 | 6 20.6
62 | 7 23.8
63 | 8 27.8
64 | 9 34.2
65 | 10 42.2
66 |
67 |
68 | ###mic_framesPerPacket:"2"
69 | Number of Speex speech frames transmitted in a packet (message). Each frame is 20 ms long. The default value is two frames per packet.
70 |
71 | The more Speex frames in a message, the lower the bandwidth required but the longer the delay in sending the message. Fewer Speex frames increases bandwidth required but reduces delay.
72 |
73 |
74 | ###mic_gain:"60"
75 | The amount by which the microphone boosts the signal. Valid values are 0 to 100. The default value is 50.
76 |
77 | ###mic_noiseSuppressionLevel: "0"
78 | Maximum attenuation of the noise in dB (negative number) used for Speex encoder. If enabled, noise suppression is applied to sound captured from Microphone before Speex compression. Set to 0 to disable noise suppression. Noise suppression is enabled by default with maximum attenuation of -30 dB. Ignored when Nellymoser codec is selected.
79 |
80 |
81 |
82 | ###mic_rate: "44"
83 | The rate at which the microphone is capturing sound, in kHz. Acceptable values are 5, 8, 11, 22, and 44. The default value is 8 kHz if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that your sound capture device supports, usually 11 kHz.
84 |
85 | Note: The actual rate differs slightly from the rate value, as noted in the following table:
86 |
87 | rate value Actual frequency
88 | 44 44,100 Hz
89 | 22 22,050 Hz
90 | 11 11,025 Hz
91 | 8 8,000 Hz
92 | 5 5,512 Hz
93 |
94 | ###mic_silenceLevel: "0"
95 | The amount of sound required to activate the microphone and dispatch the activity event. The default value is 10.
96 |
97 | ###mic_silenceTimeout: "5000"
98 | The number of milliseconds between the time the microphone stops detecting sound and the time the activity event is dispatched. The default value is 2000 (2 seconds).
99 |
100 | To set this value, use the Microphone.setSilenceLevel() method.
101 |
102 | ##desired functionalities
103 |
104 | - Login/Passwd for stream authorization.
105 | - Reconnect the stream if it lost connection with the server due to bandwith issues ( https://helpx.adobe.com/adobe-media-server/dev/reconnecting-streams-connection-drops.html)
106 | - Webcam&mic selection via javascript to improve interface.
107 | - Bandwith speedtest and other metrics to know if the stream is arriving well to the server.
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/send_h264.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
57 |
58 |
59 |
60 |
64 |
65 |
66 | To view this page ensure that Adobe Flash Player version
67 | 11.4.0 or greater is installed.
68 |
69 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Either scripts and active content are not permitted to run or Adobe Flash Player version
93 | 11.4.0 or greater is not installed.
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/src/H264_send.as:
--------------------------------------------------------------------------------
1 | package
2 | {
3 | // import flash.display.DisplayObject;
4 |
5 | import flash.text.*;
6 | import flash.text.engine.*;
7 | import flash.display.Sprite;
8 | import flash.events.NetStatusEvent;
9 | import flash.media.Camera;
10 | import flash.media.H264Level;
11 | import flash.media.H264Profile;
12 | import flash.media.H264VideoStreamSettings;
13 | import flash.media.Microphone;
14 | import flash.media.MicrophoneEnhancedOptions;
15 | import flash.media.MicrophoneEnhancedMode;
16 | import flash.media.SoundCodec;
17 | import flash.media.Video;
18 | import flash.net.NetConnection;
19 | import flash.net.NetStream;
20 |
21 | //import flash.text.TextField;
22 |
23 | [SWF( width="259", height="195" )]
24 | public class H264_send extends Sprite
25 | {
26 | // private var vid_outDescription:TextField = new TextField();
27 |
28 | //Define a NetConnection variable nc
29 | private var nc:NetConnection;
30 | //Define two NetStream variables, ns_in and ns_out
31 | private var ns_out:NetStream;
32 | //Define a Camera variable cam
33 | private var cam:Camera = Camera.getCamera();
34 | private var mic:Microphone= Microphone.getEnhancedMicrophone();
35 | private var options:MicrophoneEnhancedOptions = new MicrophoneEnhancedOptions();
36 | //private var options:MicrophoneEnhancedOptions = new MicrophoneEnhancedOptions();
37 | //to get flashvars
38 | private var flashvars:Object = root.loaderInfo.parameters as Object;
39 | //Define a Video variable named vid_out
40 | private var vid_out:Video;
41 | //Define a Video variable named vid_in
42 | //Class constructor
43 |
44 |
45 | public function H264_send()
46 | {
47 | //Call initConnection()
48 | initConnection();
49 | }
50 |
51 | //Called from class constructor, this function establishes a new NetConnection and listens for its status
52 | private function initConnection():void
53 | {
54 | //Create a new NetConnection by instantiating nc
55 | nc = new NetConnection();
56 | //Add an EventListener to listen for onNetStatus()
57 | nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
58 | //Connect to the live folder on the server
59 | nc.connect(flashvars.urlStreaming);
60 | //Tell the NetConnection where the server should invoke callback methods
61 | nc.client = this;
62 |
63 | //Instantiate the vid_out variable, set its location in the UI, and add it to the stage
64 | vid_out = new Video(347,195);
65 | vid_out.x = -44;
66 | vid_out.y = 0;
67 | vid_out.z = 0;
68 | addChild( vid_out );
69 | }
70 |
71 | //It's a best practice to always check for a successful NetConnection
72 | protected function onNetStatus(event:NetStatusEvent):void
73 | {
74 | //Trace the value of event.info.code
75 | trace( event.info.code );
76 | /*Check for a successful NetConnection, and if successful
77 | call publishCamera(), displayPublishingVideo(), and displayPlaybackVideo()*/
78 | if( event.info.code == "NetConnection.Connect.Success" )
79 | {
80 | publishCamera();
81 | displayPublishingVideo();
82 | }
83 | }
84 |
85 | //The encoding settings are set on the publishing stream
86 | protected function publishCamera():void
87 | {
88 | //Instantiate the ns_out NetStream
89 | ns_out = new NetStream( nc );
90 | //Attach the camera to the outgoing NetStream
91 | ns_out.attachCamera( cam );
92 | ns_out.attachAudio( mic );
93 | //Define a local variable named h264Settings of type H264VideoStreamSettings
94 | var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings();
95 | //Set encoding profile and level on h264Settings
96 | h264Settings.setProfileLevel(H264Profile.MAIN,H264Level.LEVEL_5_1);
97 | //Set the bitrate and quality settings on the Camera object
98 | cam.setQuality( flashvars.camera_bandwidth, flashvars.camera_quality );
99 | //Set the video's width,height, fps, and whether it should maintain its capture size
100 | cam.setMode( 347,195,flashvars.camera_fps , true );
101 | //Set the keyframe interval
102 | cam.setKeyFrameInterval( 15 );
103 |
104 |
105 | mic.setLoopBack(false);
106 | mic.gain = flashvars.mic_gain;
107 | mic.setSilenceLevel(flashvars.mic_silenceLevel,flashvars.mic_silenceTimeout);
108 | mic.codec= SoundCodec.SPEEX;
109 | mic.encodeQuality=flashvars.mic_encodeQuality;
110 | mic.framesPerPacket= flashvars.mic_framesPerPacket;
111 | mic.noiseSuppressionLevel=flashvars.mic_noiseSuppressionLevel;
112 | mic.rate= flashvars.mic_rate;
113 | options.mode = MicrophoneEnhancedMode.FULL_DUPLEX;
114 | options.echoPath = 128;
115 | options.nonLinearProcessing = true;
116 |
117 |
118 |
119 |
120 |
121 |
122 | //Set the outgoing video's compression settings based on h264Settings
123 | ns_out.videoStreamSettings = h264Settings;
124 |
125 | //Publish the outgoing stream
126 | ns_out.publish( flashvars.streamName, "live" );
127 | //Declare the metadata variable
128 | var metaData:Object = new Object();
129 | //Give the metadata object properties to reflect the stream's metadata
130 | metaData.codec = mic.codec;
131 | metaData.profile = h264Settings.profile;
132 | metaData.level = h264Settings.level;
133 | metaData.fps = cam.fps;
134 | metaData.bandwith = cam.bandwidth;
135 | metaData.height = cam.height;
136 | metaData.width = cam.width;
137 | metaData.keyFrameInterval = cam.keyFrameInterval;
138 | //Call send() on the ns_out NetStream
139 | ns_out.send( "@setDataFrame", "onMetaData", metaData );
140 |
141 | /* display text feedback
142 |
143 |
144 | var myTextBox:TextField = new TextField();
145 | var myText:String = mic.codec;
146 | var format:ElementFormat = new ElementFormat();
147 | var textElement:TextElement = new TextElement(myText, format);
148 | var textBlock:TextBlock = new TextBlock();
149 | textBlock.content = textElement;
150 | var textLine1:TextLine = textBlock.createTextLine(null, 300);
151 |
152 | textLine1.x = 30;
153 | textLine1.y = 30;
154 | textLine1.z = 1;
155 | addChild(textLine1);
156 | /**/
157 |
158 | }
159 |
160 | //Display the outgoing video stream in the UI
161 | protected function displayPublishingVideo():void
162 | {
163 | //Attach the incoming video stream to the vid_out component
164 | vid_out.attachCamera( cam );
165 | }
166 |
167 | //Display the incoming video stream in the UI
168 |
169 | //Necessary callback function that checks bandwith (remains empty in this case)
170 | public function onBWDone():void
171 | {
172 | }
173 |
174 | //Display stream metadata and lays out visual components in the UI
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | }
183 | }
--------------------------------------------------------------------------------
/js/h264_swfobject.js:
--------------------------------------------------------------------------------
1 | /*! SWFObject v2.2
2 | is released under the MIT License
3 | */
4 |
5 | var swfobject = function() {
6 |
7 | var UNDEF = "undefined",
8 | OBJECT = "object",
9 | SHOCKWAVE_FLASH = "Shockwave Flash",
10 | SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
11 | FLASH_MIME_TYPE = "application/x-shockwave-flash",
12 | EXPRESS_INSTALL_ID = "SWFObjectExprInst",
13 | ON_READY_STATE_CHANGE = "onreadystatechange",
14 |
15 | win = window,
16 | doc = document,
17 | nav = navigator,
18 |
19 | plugin = false,
20 | domLoadFnArr = [main],
21 | regObjArr = [],
22 | objIdArr = [],
23 | listenersArr = [],
24 | storedAltContent,
25 | storedAltContentId,
26 | storedCallbackFn,
27 | storedCallbackObj,
28 | isDomLoaded = false,
29 | isExpressInstallActive = false,
30 | dynamicStylesheet,
31 | dynamicStylesheetMedia,
32 | autoHideShow = true,
33 |
34 | /* Centralized function for browser feature detection
35 | - User agent string detection is only used when no good alternative is possible
36 | - Is executed directly for optimal performance
37 | */
38 | ua = function() {
39 | var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
40 | u = nav.userAgent.toLowerCase(),
41 | p = nav.platform.toLowerCase(),
42 | windows = p ? /win/.test(p) : /win/.test(u),
43 | mac = p ? /mac/.test(p) : /mac/.test(u),
44 | webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
45 | ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
46 | playerVersion = [0,0,0],
47 | d = null;
48 | if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
49 | d = nav.plugins[SHOCKWAVE_FLASH].description;
50 | if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
51 | plugin = true;
52 | ie = false; // cascaded feature detection for Internet Explorer
53 | d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
54 | playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
55 | playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
56 | playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
57 | }
58 | }
59 | else if (typeof win.ActiveXObject != UNDEF) {
60 | try {
61 | var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
62 | if (a) { // a will return null when ActiveX is disabled
63 | d = a.GetVariable("$version");
64 | if (d) {
65 | ie = true; // cascaded feature detection for Internet Explorer
66 | d = d.split(" ")[1].split(",");
67 | playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
68 | }
69 | }
70 | }
71 | catch(e) {}
72 | }
73 | return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
74 | }(),
75 |
76 | /* Cross-browser onDomLoad
77 | - Will fire an event as soon as the DOM of a web page is loaded
78 | - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/
79 | - Regular onload serves as fallback
80 | */
81 | onDomLoad = function() {
82 | if (!ua.w3) { return; }
83 | if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically
84 | callDomLoadFunctions();
85 | }
86 | if (!isDomLoaded) {
87 | if (typeof doc.addEventListener != UNDEF) {
88 | doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
89 | }
90 | if (ua.ie && ua.win) {
91 | doc.attachEvent(ON_READY_STATE_CHANGE, function() {
92 | if (doc.readyState == "complete") {
93 | doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
94 | callDomLoadFunctions();
95 | }
96 | });
97 | if (win == top) { // if not inside an iframe
98 | (function(){
99 | if (isDomLoaded) { return; }
100 | try {
101 | doc.documentElement.doScroll("left");
102 | }
103 | catch(e) {
104 | setTimeout(arguments.callee, 0);
105 | return;
106 | }
107 | callDomLoadFunctions();
108 | })();
109 | }
110 | }
111 | if (ua.wk) {
112 | (function(){
113 | if (isDomLoaded) { return; }
114 | if (!/loaded|complete/.test(doc.readyState)) {
115 | setTimeout(arguments.callee, 0);
116 | return;
117 | }
118 | callDomLoadFunctions();
119 | })();
120 | }
121 | addLoadEvent(callDomLoadFunctions);
122 | }
123 | }();
124 |
125 | function callDomLoadFunctions() {
126 | if (isDomLoaded) { return; }
127 | try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early
128 | var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
129 | t.parentNode.removeChild(t);
130 | }
131 | catch (e) { return; }
132 | isDomLoaded = true;
133 | var dl = domLoadFnArr.length;
134 | for (var i = 0; i < dl; i++) {
135 | domLoadFnArr[i]();
136 | }
137 | }
138 |
139 | function addDomLoadEvent(fn) {
140 | if (isDomLoaded) {
141 | fn();
142 | }
143 | else {
144 | domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
145 | }
146 | }
147 |
148 | /* Cross-browser onload
149 | - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
150 | - Will fire an event as soon as a web page including all of its assets are loaded
151 | */
152 | function addLoadEvent(fn) {
153 | if (typeof win.addEventListener != UNDEF) {
154 | win.addEventListener("load", fn, false);
155 | }
156 | else if (typeof doc.addEventListener != UNDEF) {
157 | doc.addEventListener("load", fn, false);
158 | }
159 | else if (typeof win.attachEvent != UNDEF) {
160 | addListener(win, "onload", fn);
161 | }
162 | else if (typeof win.onload == "function") {
163 | var fnOld = win.onload;
164 | win.onload = function() {
165 | fnOld();
166 | fn();
167 | };
168 | }
169 | else {
170 | win.onload = fn;
171 | }
172 | }
173 |
174 | /* Main function
175 | - Will preferably execute onDomLoad, otherwise onload (as a fallback)
176 | */
177 | function main() {
178 | if (plugin) {
179 | testPlayerVersion();
180 | }
181 | else {
182 | matchVersions();
183 | }
184 | }
185 |
186 | /* Detect the Flash Player version for non-Internet Explorer browsers
187 | - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description:
188 | a. Both release and build numbers can be detected
189 | b. Avoid wrong descriptions by corrupt installers provided by Adobe
190 | c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports
191 | - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available
192 | */
193 | function testPlayerVersion() {
194 | var b = doc.getElementsByTagName("body")[0];
195 | var o = createElement(OBJECT);
196 | o.setAttribute("type", FLASH_MIME_TYPE);
197 | var t = b.appendChild(o);
198 | if (t) {
199 | var counter = 0;
200 | (function(){
201 | if (typeof t.GetVariable != UNDEF) {
202 | var d = t.GetVariable("$version");
203 | if (d) {
204 | d = d.split(" ")[1].split(",");
205 | ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
206 | }
207 | }
208 | else if (counter < 10) {
209 | counter++;
210 | setTimeout(arguments.callee, 10);
211 | return;
212 | }
213 | b.removeChild(o);
214 | t = null;
215 | matchVersions();
216 | })();
217 | }
218 | else {
219 | matchVersions();
220 | }
221 | }
222 |
223 | /* Perform Flash Player and SWF version matching; static publishing only
224 | */
225 | function matchVersions() {
226 | var rl = regObjArr.length;
227 | if (rl > 0) {
228 | for (var i = 0; i < rl; i++) { // for each registered object element
229 | var id = regObjArr[i].id;
230 | var cb = regObjArr[i].callbackFn;
231 | var cbObj = {success:false, id:id};
232 | if (ua.pv[0] > 0) {
233 | var obj = getElementById(id);
234 | if (obj) {
235 | if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match!
236 | setVisibility(id, true);
237 | if (cb) {
238 | cbObj.success = true;
239 | cbObj.ref = getObjectById(id);
240 | cb(cbObj);
241 | }
242 | }
243 | else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported
244 | var att = {};
245 | att.data = regObjArr[i].expressInstall;
246 | att.width = obj.getAttribute("width") || "0";
247 | att.height = obj.getAttribute("height") || "0";
248 | if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
249 | if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
250 | // parse HTML object param element's name-value pairs
251 | var par = {};
252 | var p = obj.getElementsByTagName("param");
253 | var pl = p.length;
254 | for (var j = 0; j < pl; j++) {
255 | if (p[j].getAttribute("name").toLowerCase() != "movie") {
256 | par[p[j].getAttribute("name")] = p[j].getAttribute("value");
257 | }
258 | }
259 | showExpressInstall(att, par, id, cb);
260 | }
261 | else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF
262 | displayAltContent(obj);
263 | if (cb) { cb(cbObj); }
264 | }
265 | }
266 | }
267 | else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content)
268 | setVisibility(id, true);
269 | if (cb) {
270 | var o = getObjectById(id); // test whether there is an HTML object element or not
271 | if (o && typeof o.SetVariable != UNDEF) {
272 | cbObj.success = true;
273 | cbObj.ref = o;
274 | }
275 | cb(cbObj);
276 | }
277 | }
278 | }
279 | }
280 | }
281 |
282 | function getObjectById(objectIdStr) {
283 | var r = null;
284 | var o = getElementById(objectIdStr);
285 | if (o && o.nodeName == "OBJECT") {
286 | if (typeof o.SetVariable != UNDEF) {
287 | r = o;
288 | }
289 | else {
290 | var n = o.getElementsByTagName(OBJECT)[0];
291 | if (n) {
292 | r = n;
293 | }
294 | }
295 | }
296 | return r;
297 | }
298 |
299 | /* Requirements for Adobe Express Install
300 | - only one instance can be active at a time
301 | - fp 6.0.65 or higher
302 | - Win/Mac OS only
303 | - no Webkit engines older than version 312
304 | */
305 | function canExpressInstall() {
306 | return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
307 | }
308 |
309 | /* Show the Adobe Express Install dialog
310 | - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
311 | */
312 | function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
313 | isExpressInstallActive = true;
314 | storedCallbackFn = callbackFn || null;
315 | storedCallbackObj = {success:false, id:replaceElemIdStr};
316 | var obj = getElementById(replaceElemIdStr);
317 | if (obj) {
318 | if (obj.nodeName == "OBJECT") { // static publishing
319 | storedAltContent = abstractAltContent(obj);
320 | storedAltContentId = null;
321 | }
322 | else { // dynamic publishing
323 | storedAltContent = obj;
324 | storedAltContentId = replaceElemIdStr;
325 | }
326 | att.id = EXPRESS_INSTALL_ID;
327 | if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
328 | if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
329 | doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
330 | var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
331 | fv = "MMredirectURL=" + encodeURI(window.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
332 | if (typeof par.flashvars != UNDEF) {
333 | par.flashvars += "&" + fv;
334 | }
335 | else {
336 | par.flashvars = fv;
337 | }
338 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
339 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
340 | if (ua.ie && ua.win && obj.readyState != 4) {
341 | var newObj = createElement("div");
342 | replaceElemIdStr += "SWFObjectNew";
343 | newObj.setAttribute("id", replaceElemIdStr);
344 | obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf
345 | obj.style.display = "none";
346 | (function(){
347 | if (obj.readyState == 4) {
348 | obj.parentNode.removeChild(obj);
349 | }
350 | else {
351 | setTimeout(arguments.callee, 10);
352 | }
353 | })();
354 | }
355 | createSWF(att, par, replaceElemIdStr);
356 | }
357 | }
358 |
359 | /* Functions to abstract and display alternative content
360 | */
361 | function displayAltContent(obj) {
362 | if (ua.ie && ua.win && obj.readyState != 4) {
363 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
364 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
365 | var el = createElement("div");
366 | obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content
367 | el.parentNode.replaceChild(abstractAltContent(obj), el);
368 | obj.style.display = "none";
369 | (function(){
370 | if (obj.readyState == 4) {
371 | obj.parentNode.removeChild(obj);
372 | }
373 | else {
374 | setTimeout(arguments.callee, 10);
375 | }
376 | })();
377 | }
378 | else {
379 | obj.parentNode.replaceChild(abstractAltContent(obj), obj);
380 | }
381 | }
382 |
383 | function abstractAltContent(obj) {
384 | var ac = createElement("div");
385 | if (ua.win && ua.ie) {
386 | ac.innerHTML = obj.innerHTML;
387 | }
388 | else {
389 | var nestedObj = obj.getElementsByTagName(OBJECT)[0];
390 | if (nestedObj) {
391 | var c = nestedObj.childNodes;
392 | if (c) {
393 | var cl = c.length;
394 | for (var i = 0; i < cl; i++) {
395 | if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
396 | ac.appendChild(c[i].cloneNode(true));
397 | }
398 | }
399 | }
400 | }
401 | }
402 | return ac;
403 | }
404 |
405 | /* Cross-browser dynamic SWF creation
406 | */
407 | function createSWF(attObj, parObj, id) {
408 | var r, el = getElementById(id);
409 | if (ua.wk && ua.wk < 312) { return r; }
410 | if (el) {
411 | if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
412 | attObj.id = id;
413 | }
414 | if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML
415 | var att = "";
416 | for (var i in attObj) {
417 | if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries
418 | if (i.toLowerCase() == "data") {
419 | parObj.movie = attObj[i];
420 | }
421 | else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
422 | att += ' class="' + attObj[i] + '"';
423 | }
424 | else if (i.toLowerCase() != "classid") {
425 | att += ' ' + i + '="' + attObj[i] + '"';
426 | }
427 | }
428 | }
429 | var par = "";
430 | for (var j in parObj) {
431 | if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries
432 | par += ' ';
433 | }
434 | }
435 | el.outerHTML = '' + par + ' ';
436 | objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only)
437 | r = getElementById(attObj.id);
438 | }
439 | else { // well-behaving browsers
440 | var o = createElement(OBJECT);
441 | o.setAttribute("type", FLASH_MIME_TYPE);
442 | for (var m in attObj) {
443 | if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries
444 | if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
445 | o.setAttribute("class", attObj[m]);
446 | }
447 | else if (m.toLowerCase() != "classid") { // filter out IE specific attribute
448 | o.setAttribute(m, attObj[m]);
449 | }
450 | }
451 | }
452 | for (var n in parObj) {
453 | if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element
454 | createObjParam(o, n, parObj[n]);
455 | }
456 | }
457 | el.parentNode.replaceChild(o, el);
458 | r = o;
459 | }
460 | }
461 | return r;
462 | }
463 |
464 | function createObjParam(el, pName, pValue) {
465 | var p = createElement("param");
466 | p.setAttribute("name", pName);
467 | p.setAttribute("value", pValue);
468 | el.appendChild(p);
469 | }
470 |
471 | /* Cross-browser SWF removal
472 | - Especially needed to safely and completely remove a SWF in Internet Explorer
473 | */
474 | function removeSWF(id) {
475 | var obj = getElementById(id);
476 | if (obj && obj.nodeName == "OBJECT") {
477 | if (ua.ie && ua.win) {
478 | obj.style.display = "none";
479 | (function(){
480 | if (obj.readyState == 4) {
481 | removeObjectInIE(id);
482 | }
483 | else {
484 | setTimeout(arguments.callee, 10);
485 | }
486 | })();
487 | }
488 | else {
489 | obj.parentNode.removeChild(obj);
490 | }
491 | }
492 | }
493 |
494 | function removeObjectInIE(id) {
495 | var obj = getElementById(id);
496 | if (obj) {
497 | for (var i in obj) {
498 | if (typeof obj[i] == "function") {
499 | obj[i] = null;
500 | }
501 | }
502 | obj.parentNode.removeChild(obj);
503 | }
504 | }
505 |
506 | /* Functions to optimize JavaScript compression
507 | */
508 | function getElementById(id) {
509 | var el = null;
510 | try {
511 | el = doc.getElementById(id);
512 | }
513 | catch (e) {}
514 | return el;
515 | }
516 |
517 | function createElement(el) {
518 | return doc.createElement(el);
519 | }
520 |
521 | /* Updated attachEvent function for Internet Explorer
522 | - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
523 | */
524 | function addListener(target, eventType, fn) {
525 | target.attachEvent(eventType, fn);
526 | listenersArr[listenersArr.length] = [target, eventType, fn];
527 | }
528 |
529 | /* Flash Player and SWF content version matching
530 | */
531 | function hasPlayerVersion(rv) {
532 | var pv = ua.pv, v = rv.split(".");
533 | v[0] = parseInt(v[0], 10);
534 | v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
535 | v[2] = parseInt(v[2], 10) || 0;
536 | return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
537 | }
538 |
539 | /* Cross-browser dynamic CSS creation
540 | - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
541 | */
542 | function createCSS(sel, decl, media, newStyle) {
543 | if (ua.ie && ua.mac) { return; }
544 | var h = doc.getElementsByTagName("head")[0];
545 | if (!h) { return; } // to also support badly authored HTML pages that lack a head element
546 | var m = (media && typeof media == "string") ? media : "screen";
547 | if (newStyle) {
548 | dynamicStylesheet = null;
549 | dynamicStylesheetMedia = null;
550 | }
551 | if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
552 | // create dynamic stylesheet + get a global reference to it
553 | var s = createElement("style");
554 | s.setAttribute("type", "text/css");
555 | s.setAttribute("media", m);
556 | dynamicStylesheet = h.appendChild(s);
557 | if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
558 | dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
559 | }
560 | dynamicStylesheetMedia = m;
561 | }
562 | // add style rule
563 | if (ua.ie && ua.win) {
564 | if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
565 | dynamicStylesheet.addRule(sel, decl);
566 | }
567 | }
568 | else {
569 | if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
570 | dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
571 | }
572 | }
573 | }
574 |
575 | function setVisibility(id, isVisible) {
576 | if (!autoHideShow) { return; }
577 | var v = isVisible ? "visible" : "hidden";
578 | if (isDomLoaded && getElementById(id)) {
579 | getElementById(id).style.visibility = v;
580 | }
581 | else {
582 | createCSS("#" + id, "visibility:" + v);
583 | }
584 | }
585 |
586 | /* Filter to avoid XSS attacks
587 | */
588 | function urlEncodeIfNecessary(s) {
589 | var regex = /[\\\"<>\.;]/;
590 | var hasBadChars = regex.exec(s) != null;
591 | return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
592 | }
593 |
594 | /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
595 | */
596 | var cleanup = function() {
597 | if (ua.ie && ua.win) {
598 | window.attachEvent("onunload", function() {
599 | // remove listeners to avoid memory leaks
600 | var ll = listenersArr.length;
601 | for (var i = 0; i < ll; i++) {
602 | listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
603 | }
604 | // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
605 | var il = objIdArr.length;
606 | for (var j = 0; j < il; j++) {
607 | removeSWF(objIdArr[j]);
608 | }
609 | // cleanup library's main closures to avoid memory leaks
610 | for (var k in ua) {
611 | ua[k] = null;
612 | }
613 | ua = null;
614 | for (var l in swfobject) {
615 | swfobject[l] = null;
616 | }
617 | swfobject = null;
618 | });
619 | }
620 | }();
621 |
622 | return {
623 | /* Public API
624 | - Reference: http://code.google.com/p/swfobject/wiki/documentation
625 | */
626 | registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
627 | if (ua.w3 && objectIdStr && swfVersionStr) {
628 | var regObj = {};
629 | regObj.id = objectIdStr;
630 | regObj.swfVersion = swfVersionStr;
631 | regObj.expressInstall = xiSwfUrlStr;
632 | regObj.callbackFn = callbackFn;
633 | regObjArr[regObjArr.length] = regObj;
634 | setVisibility(objectIdStr, false);
635 | }
636 | else if (callbackFn) {
637 | callbackFn({success:false, id:objectIdStr});
638 | }
639 | },
640 |
641 | getObjectById: function(objectIdStr) {
642 | if (ua.w3) {
643 | return getObjectById(objectIdStr);
644 | }
645 | },
646 |
647 | embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
648 | var callbackObj = {success:false, id:replaceElemIdStr};
649 | if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
650 | setVisibility(replaceElemIdStr, false);
651 | addDomLoadEvent(function() {
652 | widthStr += ""; // auto-convert to string
653 | heightStr += "";
654 | var att = {};
655 | if (attObj && typeof attObj === OBJECT) {
656 | for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs
657 | att[i] = attObj[i];
658 | }
659 | }
660 | att.data = swfUrlStr;
661 | att.width = widthStr;
662 | att.height = heightStr;
663 | var par = {};
664 | if (parObj && typeof parObj === OBJECT) {
665 | for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs
666 | par[j] = parObj[j];
667 | }
668 | }
669 | if (flashvarsObj && typeof flashvarsObj === OBJECT) {
670 | for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs
671 | if (typeof par.flashvars != UNDEF) {
672 | par.flashvars += "&" + k + "=" + flashvarsObj[k];
673 | }
674 | else {
675 | par.flashvars = k + "=" + flashvarsObj[k];
676 | }
677 | }
678 | }
679 | if (hasPlayerVersion(swfVersionStr)) { // create SWF
680 | var obj = createSWF(att, par, replaceElemIdStr);
681 | if (att.id == replaceElemIdStr) {
682 | setVisibility(replaceElemIdStr, true);
683 | }
684 | callbackObj.success = true;
685 | callbackObj.ref = obj;
686 | }
687 | else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install
688 | att.data = xiSwfUrlStr;
689 | showExpressInstall(att, par, replaceElemIdStr, callbackFn);
690 | return;
691 | }
692 | else { // show alternative content
693 | setVisibility(replaceElemIdStr, true);
694 | }
695 | if (callbackFn) { callbackFn(callbackObj); }
696 | });
697 | }
698 | else if (callbackFn) { callbackFn(callbackObj); }
699 | },
700 |
701 | switchOffAutoHideShow: function() {
702 | autoHideShow = false;
703 | },
704 |
705 | ua: ua,
706 |
707 | getFlashPlayerVersion: function() {
708 | return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
709 | },
710 |
711 | hasFlashPlayerVersion: hasPlayerVersion,
712 |
713 | createSWF: function(attObj, parObj, replaceElemIdStr) {
714 | if (ua.w3) {
715 | return createSWF(attObj, parObj, replaceElemIdStr);
716 | }
717 | else {
718 | return undefined;
719 | }
720 | },
721 |
722 | showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
723 | if (ua.w3 && canExpressInstall()) {
724 | showExpressInstall(att, par, replaceElemIdStr, callbackFn);
725 | }
726 | },
727 |
728 | removeSWF: function(objElemIdStr) {
729 | if (ua.w3) {
730 | removeSWF(objElemIdStr);
731 | }
732 | },
733 |
734 | createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
735 | if (ua.w3) {
736 | createCSS(selStr, declStr, mediaStr, newStyleBoolean);
737 | }
738 | },
739 |
740 | addDomLoadEvent: addDomLoadEvent,
741 |
742 | addLoadEvent: addLoadEvent,
743 |
744 | getQueryParamValue: function(param) {
745 | var q = doc.location.search || doc.location.hash;
746 | if (q) {
747 | if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark
748 | if (param == null) {
749 | return urlEncodeIfNecessary(q);
750 | }
751 | var pairs = q.split("&");
752 | for (var i = 0; i < pairs.length; i++) {
753 | if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
754 | return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
755 | }
756 | }
757 | }
758 | return "";
759 | },
760 |
761 | // For internal usage only
762 | expressInstallCallback: function() {
763 | if (isExpressInstallActive) {
764 | var obj = getElementById(EXPRESS_INSTALL_ID);
765 | if (obj && storedAltContent) {
766 | obj.parentNode.replaceChild(storedAltContent, obj);
767 | if (storedAltContentId) {
768 | setVisibility(storedAltContentId, true);
769 | if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
770 | }
771 | if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
772 | }
773 | isExpressInstallActive = false;
774 | }
775 | }
776 | };
777 | }();
778 |
--------------------------------------------------------------------------------