├── .actionScriptProperties ├── .externalToolBuilders └── org.eclipse.wst.validation.validationbuilder.launch ├── .flexProperties ├── .project ├── .settings ├── com.adobe.flexbuilder.project.prefs └── org.eclipse.core.resources.prefs ├── LICENSING ├── README.markdown ├── docs └── docs.wiki ├── html-template ├── AC_OETags.js ├── history │ ├── history.css │ ├── history.js │ └── historyFrame.html ├── index.template.html └── playerProductInstall.swf ├── libs └── empty └── src ├── ABPlayer.mxml ├── assets ├── bar.png ├── bt_play.png ├── btn_cfgS_down.png ├── btn_cfgS_up.png ├── btn_cfg_down.png ├── btn_cfg_up.png ├── btn_dmk_down.png ├── btn_dmk_off_down.png ├── btn_dmk_off_up.png ├── btn_dmk_up.png ├── btn_fs_down.png ├── btn_fs_up.png ├── btn_pause_down.png ├── btn_pause_up.png ├── btn_play_down.png ├── btn_play_up.png ├── btn_sound_down.png ├── btn_sound_mute_down.png ├── btn_sound_mute_up.png ├── btn_sound_up.png ├── btn_ss_down.png ├── btn_ss_up.png ├── btn_stop_down.png ├── btn_stop_up.png ├── trackerThumb.png └── trackerThumbDown.png ├── com └── adobe │ └── serialization │ └── json │ ├── JSON.as │ ├── JSONDecoder.as │ ├── JSONEncoder.as │ ├── JSONParseError.as │ ├── JSONToken.as │ ├── JSONTokenType.as │ └── JSONTokenizer.as ├── css └── style.css ├── org └── kanoha │ ├── collections │ ├── LinkedList.as │ ├── Node.as │ └── SortedLinkedList.as │ ├── comment │ ├── BottomCommentSpaceAllocator.as │ ├── Comment.as │ ├── CommentConfig.as │ ├── CommentFilter.as │ ├── CommentManager.as │ ├── CommentSpaceAllocator.as │ ├── ScrollCommentSpaceAllocator.as │ └── effects │ │ └── TimelineEffect.as │ ├── events │ ├── LoaderEvent.as │ ├── NMEvent.as │ └── NSEvent.as │ ├── net │ ├── CommentLoader.as │ ├── CommentSender.as │ ├── VideoLoader.as │ └── XMLLoader.as │ ├── util │ ├── CommentListParser.as │ └── Filters.as │ └── video │ ├── NS.as │ ├── NSComponent.as │ └── VideoInfo.as └── skins ├── btnCommentOffSkin.mxml ├── btnCommentOnSkin.mxml ├── btnFullScreenSkin.mxml ├── btnMuteSkin.mxml ├── btnPauseSkin.mxml ├── btnPlaySkin.mxml ├── btnSidebarOffSkin.mxml ├── btnSidebarOnSkin.mxml ├── btnSmallScreenSkin.mxml ├── btnStopSkin.mxml ├── btnVolumeSkin.mxml └── containerBar.mxml /.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.externalToolBuilders/org.eclipse.wst.validation.validationbuilder.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.flexProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ABPlayer 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.ui.externaltools.ExternalToolBuilder 15 | full,incremental, 16 | 17 | 18 | LaunchConfigHandle 19 | <project>/.externalToolBuilders/org.eclipse.wst.validation.validationbuilder.launch 20 | 21 | 22 | 23 | 24 | 25 | com.adobe.flexbuilder.project.flexnature 26 | com.adobe.flexbuilder.project.actionscriptnature 27 | 28 | 29 | 30 | bin-debug 31 | 2 32 | C:/www/ABPlayer 33 | 34 | 35 | bin-release 36 | 2 37 | C:/www/ABPlayer 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /.settings/com.adobe.flexbuilder.project.prefs: -------------------------------------------------------------------------------- 1 | #Wed Sep 21 13:20:17 CST 2011 2 | eclipse.preferences.version=1 3 | upgradeSDK/fb4= 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Sat Sep 03 15:43:06 CST 2011 2 | eclipse.preferences.version=1 3 | encoding/=utf-8 4 | -------------------------------------------------------------------------------- /LICENSING: -------------------------------------------------------------------------------- 1 | LICENSING 2 | ========================= 3 | As of Version 1.0.11 ABPlayer will be solely licensed under the MIT License 4 | Versions before 1.0.11 are still under the GPLv2. 5 | 6 | ========================= 7 | Copyright (c) 2012 Jim Chen ( CQZ, Jabbany ) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in the 11 | Software without restriction, including without limitation the rights to use, copy, 12 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 13 | and to permit persons to whom the Software is furnished to do so, subject to the 14 | following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies 17 | or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 20 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 21 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 22 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 23 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | Other Partitions 27 | ==================================================================================== 28 | The software contains packages from Adobe that are under their corresponding license 29 | these files are located in com.adobe.serialization. The Flex Platform is also under 30 | license of Adobe Systems Incorporated. 31 | 32 | The following license is extracted from the concerning files: 33 | 34 | Copyright (c) 2008, Adobe Systems Incorporated 35 | All rights reserved. 36 | 37 | Redistribution and use in source and binary forms, with or without 38 | modification, are permitted provided that the following conditions are 39 | met: 40 | 41 | * Redistributions of source code must retain the above copyright notice, 42 | this list of conditions and the following disclaimer. 43 | 44 | * Redistributions in binary form must reproduce the above copyright 45 | notice, this list of conditions and the following disclaimer in the 46 | documentation and/or other materials provided with the distribution. 47 | 48 | * Neither the name of Adobe Systems Incorporated nor the names of its 49 | contributors may be used to endorse or promote products derived from 50 | this software without specific prior written permission. 51 | 52 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 53 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 54 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 55 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 56 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 57 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 58 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 59 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 60 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 61 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 62 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 | 64 | 65 | =============================================================================== 66 | 67 | Also this project makes use of the tamaki video container element from 68 | Project!Another Displayer, through modification and partitions of the 69 | CommentSpaceAllocation and CommentManager classes contain modified code 70 | from MukioPlayerPlus. Both projects are released under the MIT License 71 | and you can find the corresponding licenses inside corresponding projects: 72 | 73 | http://code.google.com/p/mukioplayer/ 74 | http://code.google.com/p/projectanotherdisplayer/ 75 | 76 | =============================================================================== 77 | Resources And Images 78 | 79 | Assets and images used in the project are considered to be released into the 80 | public domain. You may freely use them. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # 说明 # 2 | ABPlayer的1.0.11版本以及后续版本将不支持Flex 3 SDK 转为使用Flex 4 SDK。 3 | 4 | # Introduction # 5 | ABPlayer is a video player developed in the Flex 3 SDK. It allows users to send comments onto the playing timeline while playing online video. Comments are scrolled across the screen when the video reaches the timeline position of the comment allowing interactive and realtime commenting of the video. It also features bottom, top,positioned and reverse scroll options, that allow users to create closed captioning and subtitles for video. Comments support color, font, size and opacity and can have effects as well. 6 | 7 | ## Compiling ## 8 | ABPlayer is based on the Flex 3.2 SDK, you can compile this in Flash Builder 4.5 by setting the SDK to the older SDK. The css needs to be modified to use the MX namespace as the project doesn't incorporate Spark components. 9 | 10 | ## Translations ## 11 | ABPlayer is by default, offered in Chinese (Simplified), you may directly edit strings to translate into any language you wish. Though some English fonts are not completely approprate for positioned comments. If you wish to open 12 | high level comments to users, you should use a monospace font available cross-platform. 13 | 14 | ## 中文 ## 15 | ABPlayer是一个Flex 3 SDK/Flex 4 SDK下开发的弹幕播放器,它有着不同于MukioPlayer和PADPlayer的一些特性,也兼容了两个播放器的很多优点。ABPlayer有比较高的可扩展性,也是少数在Flex SDK 3 下提供的全方位弹幕播放器。 16 | ABPlayer支持滚动弹幕,顶部/底部弹幕,逆向弹幕和高级定位弹幕,并且支持渐变,色彩和字体。弹幕可以设置透明度,并且有一系列高级的过滤器(弹幕对象过滤),同时默认包含一些视频的透镜。它支持一系列JS扩展功能,包括动态载入视频/切换分P等等。 17 | 18 | 此工程为中文工程,有需求的开发者可以自行Checkout。 19 | 20 | ## Licensing/版权声明 ## 21 | See 'LICENSING' file -------------------------------------------------------------------------------- /docs/docs.wiki: -------------------------------------------------------------------------------- 1 | Documentation Coming Soon -------------------------------------------------------------------------------- /html-template/AC_OETags.js: -------------------------------------------------------------------------------- 1 | // Flash Player Version Detection - Rev 1.6 2 | // Detect Client Browser type 3 | // Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. 4 | var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; 5 | var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; 6 | var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; 7 | 8 | function ControlVersion() 9 | { 10 | var version; 11 | var axo; 12 | var e; 13 | 14 | // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry 15 | 16 | try { 17 | // version will be set for 7.X or greater players 18 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); 19 | version = axo.GetVariable("$version"); 20 | } catch (e) { 21 | } 22 | 23 | if (!version) 24 | { 25 | try { 26 | // version will be set for 6.X players only 27 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); 28 | 29 | // installed player is some revision of 6.0 30 | // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, 31 | // so we have to be careful. 32 | 33 | // default to the first public version 34 | version = "WIN 6,0,21,0"; 35 | 36 | // throws if AllowScripAccess does not exist (introduced in 6.0r47) 37 | axo.AllowScriptAccess = "always"; 38 | 39 | // safe to call for 6.0r47 or greater 40 | version = axo.GetVariable("$version"); 41 | 42 | } catch (e) { 43 | } 44 | } 45 | 46 | if (!version) 47 | { 48 | try { 49 | // version will be set for 4.X or 5.X player 50 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 51 | version = axo.GetVariable("$version"); 52 | } catch (e) { 53 | } 54 | } 55 | 56 | if (!version) 57 | { 58 | try { 59 | // version will be set for 3.X player 60 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 61 | version = "WIN 3,0,18,0"; 62 | } catch (e) { 63 | } 64 | } 65 | 66 | if (!version) 67 | { 68 | try { 69 | // version will be set for 2.X player 70 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 71 | version = "WIN 2,0,0,11"; 72 | } catch (e) { 73 | version = -1; 74 | } 75 | } 76 | 77 | return version; 78 | } 79 | 80 | // JavaScript helper required to detect Flash Player PlugIn version information 81 | function GetSwfVer(){ 82 | // NS/Opera version >= 3 check for Flash plugin in plugin array 83 | var flashVer = -1; 84 | 85 | if (navigator.plugins != null && navigator.plugins.length > 0) { 86 | if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { 87 | var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; 88 | var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; 89 | var descArray = flashDescription.split(" "); 90 | var tempArrayMajor = descArray[2].split("."); 91 | var versionMajor = tempArrayMajor[0]; 92 | var versionMinor = tempArrayMajor[1]; 93 | var versionRevision = descArray[3]; 94 | if (versionRevision == "") { 95 | versionRevision = descArray[4]; 96 | } 97 | if (versionRevision[0] == "d") { 98 | versionRevision = versionRevision.substring(1); 99 | } else if (versionRevision[0] == "r") { 100 | versionRevision = versionRevision.substring(1); 101 | if (versionRevision.indexOf("d") > 0) { 102 | versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); 103 | } 104 | } else if (versionRevision[0] == "b") { 105 | versionRevision = versionRevision.substring(1); 106 | } 107 | var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; 108 | } 109 | } 110 | // MSN/WebTV 2.6 supports Flash 4 111 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; 112 | // WebTV 2.5 supports Flash 3 113 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; 114 | // older WebTV supports Flash 2 115 | else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; 116 | else if ( isIE && isWin && !isOpera ) { 117 | flashVer = ControlVersion(); 118 | } 119 | return flashVer; 120 | } 121 | 122 | // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available 123 | function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) 124 | { 125 | versionStr = GetSwfVer(); 126 | if (versionStr == -1 ) { 127 | return false; 128 | } else if (versionStr != 0) { 129 | if(isIE && isWin && !isOpera) { 130 | // Given "WIN 2,0,0,11" 131 | tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] 132 | tempString = tempArray[1]; // "2,0,0,11" 133 | versionArray = tempString.split(","); // ['2', '0', '0', '11'] 134 | } else { 135 | versionArray = versionStr.split("."); 136 | } 137 | var versionMajor = versionArray[0]; 138 | var versionMinor = versionArray[1]; 139 | var versionRevision = versionArray[2]; 140 | 141 | // is the major.revision >= requested major.revision AND the minor version >= requested minor 142 | if (versionMajor > parseFloat(reqMajorVer)) { 143 | return true; 144 | } else if (versionMajor == parseFloat(reqMajorVer)) { 145 | if (versionMinor > parseFloat(reqMinorVer)) 146 | return true; 147 | else if (versionMinor == parseFloat(reqMinorVer)) { 148 | if (versionRevision >= parseFloat(reqRevision)) 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | } 155 | 156 | function AC_AddExtension(src, ext) 157 | { 158 | var qIndex = src.indexOf('?'); 159 | if ( qIndex != -1) 160 | { 161 | // Add the extention (if needed) before the query params 162 | var path = src.substring(0, qIndex); 163 | if (path.length >= ext.length && path.lastIndexOf(ext) == (path.length - ext.length)) 164 | return src; 165 | else 166 | return src.replace(/\?/, ext+'?'); 167 | } 168 | else 169 | { 170 | // Add the extension (if needed) to the end of the URL 171 | if (src.length >= ext.length && src.lastIndexOf(ext) == (src.length - ext.length)) 172 | return src; // Already have extension 173 | else 174 | return src + ext; 175 | } 176 | } 177 | 178 | function AC_Generateobj(objAttrs, params, embedAttrs) 179 | { 180 | var str = ''; 181 | if (isIE && isWin && !isOpera) 182 | { 183 | str += ' '; 189 | str += ''; 190 | } else { 191 | str += ' 0 && typeof o[0].SetVariable != "undefined") { 114 | return o[0]; 115 | } 116 | else if (e.length > 0 && typeof e[0].SetVariable != "undefined") { 117 | return e[0]; 118 | } 119 | } 120 | } 121 | else { 122 | var o = document.getElementsByTagName("object"); 123 | var e = document.getElementsByTagName("embed"); 124 | if (e.length > 0 && typeof e[0].SetVariable != "undefined") { 125 | return e[0]; 126 | } 127 | else if (o.length > 0 && typeof o[0].SetVariable != "undefined") { 128 | return o[0]; 129 | } 130 | else if (o.length > 1 && typeof o[1].SetVariable != "undefined") { 131 | return o[1]; 132 | } 133 | } 134 | return undefined; 135 | } 136 | 137 | function getPlayers() { 138 | var players = []; 139 | if (players.length == 0) { 140 | var tmp = document.getElementsByTagName('object'); 141 | players = tmp; 142 | } 143 | 144 | if (players.length == 0 || players[0].object == null) { 145 | var tmp = document.getElementsByTagName('embed'); 146 | players = tmp; 147 | } 148 | return players; 149 | } 150 | 151 | function getIframeHash() { 152 | var doc = getHistoryFrame().contentWindow.document; 153 | var hash = String(doc.location.search); 154 | if (hash.length == 1 && hash.charAt(0) == "?") { 155 | hash = ""; 156 | } 157 | else if (hash.length >= 2 && hash.charAt(0) == "?") { 158 | hash = hash.substring(1); 159 | } 160 | return hash; 161 | } 162 | 163 | /* Get the current location hash excluding the '#' symbol. */ 164 | function getHash() { 165 | // It would be nice if we could use document.location.hash here, 166 | // but it's faulty sometimes. 167 | var idx = document.location.href.indexOf('#'); 168 | return (idx >= 0) ? document.location.href.substr(idx+1) : ''; 169 | } 170 | 171 | /* Get the current location hash excluding the '#' symbol. */ 172 | function setHash(hash) { 173 | // It would be nice if we could use document.location.hash here, 174 | // but it's faulty sometimes. 175 | if (hash == '') hash = '#' 176 | document.location.hash = hash; 177 | } 178 | 179 | function createState(baseUrl, newUrl, flexAppUrl) { 180 | return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; 181 | } 182 | 183 | /* Add a history entry to the browser. 184 | * baseUrl: the portion of the location prior to the '#' 185 | * newUrl: the entire new URL, including '#' and following fragment 186 | * flexAppUrl: the portion of the location following the '#' only 187 | */ 188 | function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { 189 | 190 | //delete all the history entries 191 | forwardStack = []; 192 | 193 | if (browser.ie) { 194 | //Check to see if we are being asked to do a navigate for the first 195 | //history entry, and if so ignore, because it's coming from the creation 196 | //of the history iframe 197 | if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { 198 | currentHref = initialHref; 199 | return; 200 | } 201 | if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { 202 | newUrl = baseUrl + '#' + defaultHash; 203 | flexAppUrl = defaultHash; 204 | } else { 205 | // for IE, tell the history frame to go somewhere without a '#' 206 | // in order to get this entry into the browser history. 207 | getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; 208 | } 209 | setHash(flexAppUrl); 210 | } else { 211 | 212 | //ADR 213 | if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { 214 | initialState = createState(baseUrl, newUrl, flexAppUrl); 215 | } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { 216 | backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); 217 | } 218 | 219 | if (browser.safari) { 220 | // for Safari, submit a form whose action points to the desired URL 221 | if (browser.version <= 419.3) { 222 | var file = window.location.pathname.toString(); 223 | file = file.substring(file.lastIndexOf("/")+1); 224 | getFormElement().innerHTML = '
'; 225 | //get the current elements and add them to the form 226 | var qs = window.location.search.substring(1); 227 | var qs_arr = qs.split("&"); 228 | for (var i = 0; i < qs_arr.length; i++) { 229 | var tmp = qs_arr[i].split("="); 230 | var elem = document.createElement("input"); 231 | elem.type = "hidden"; 232 | elem.name = tmp[0]; 233 | elem.value = tmp[1]; 234 | document.forms.historyForm.appendChild(elem); 235 | } 236 | document.forms.historyForm.submit(); 237 | } else { 238 | top.location.hash = flexAppUrl; 239 | } 240 | // We also have to maintain the history by hand for Safari 241 | historyHash[history.length] = flexAppUrl; 242 | _storeStates(); 243 | } else { 244 | // Otherwise, write an anchor into the page and tell the browser to go there 245 | addAnchor(flexAppUrl); 246 | setHash(flexAppUrl); 247 | } 248 | } 249 | backStack.push(createState(baseUrl, newUrl, flexAppUrl)); 250 | } 251 | 252 | function _storeStates() { 253 | if (browser.safari) { 254 | getRememberElement().value = historyHash.join(","); 255 | } 256 | } 257 | 258 | function handleBackButton() { 259 | //The "current" page is always at the top of the history stack. 260 | var current = backStack.pop(); 261 | if (!current) { return; } 262 | var last = backStack[backStack.length - 1]; 263 | if (!last && backStack.length == 0){ 264 | last = initialState; 265 | } 266 | forwardStack.push(current); 267 | } 268 | 269 | function handleForwardButton() { 270 | //summary: private method. Do not call this directly. 271 | 272 | var last = forwardStack.pop(); 273 | if (!last) { return; } 274 | backStack.push(last); 275 | } 276 | 277 | function handleArbitraryUrl() { 278 | //delete all the history entries 279 | forwardStack = []; 280 | } 281 | 282 | /* Called periodically to poll to see if we need to detect navigation that has occurred */ 283 | function checkForUrlChange() { 284 | 285 | if (browser.ie) { 286 | if (currentHref != document.location.href && currentHref + '#' != document.location.href) { 287 | //This occurs when the user has navigated to a specific URL 288 | //within the app, and didn't use browser back/forward 289 | //IE seems to have a bug where it stops updating the URL it 290 | //shows the end-user at this point, but programatically it 291 | //appears to be correct. Do a full app reload to get around 292 | //this issue. 293 | if (browser.version < 7) { 294 | currentHref = document.location.href; 295 | document.location.reload(); 296 | } else { 297 | if (getHash() != getIframeHash()) { 298 | // this.iframe.src = this.blankURL + hash; 299 | var sourceToSet = historyFrameSourcePrefix + getHash(); 300 | getHistoryFrame().src = sourceToSet; 301 | } 302 | } 303 | } 304 | } 305 | 306 | if (browser.safari) { 307 | // For Safari, we have to check to see if history.length changed. 308 | if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { 309 | //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); 310 | // If it did change, then we have to look the old state up 311 | // in our hand-maintained array since document.location.hash 312 | // won't have changed, then call back into BrowserManager. 313 | currentHistoryLength = history.length; 314 | var flexAppUrl = historyHash[currentHistoryLength]; 315 | if (flexAppUrl == '') { 316 | //flexAppUrl = defaultHash; 317 | } 318 | //ADR: to fix multiple 319 | if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { 320 | var pl = getPlayers(); 321 | for (var i = 0; i < pl.length; i++) { 322 | pl[i].browserURLChange(flexAppUrl); 323 | } 324 | } else { 325 | getPlayer().browserURLChange(flexAppUrl); 326 | } 327 | _storeStates(); 328 | } 329 | } 330 | if (browser.firefox) { 331 | if (currentHref != document.location.href) { 332 | var bsl = backStack.length; 333 | 334 | var urlActions = { 335 | back: false, 336 | forward: false, 337 | set: false 338 | } 339 | 340 | if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { 341 | urlActions.back = true; 342 | // FIXME: could this ever be a forward button? 343 | // we can't clear it because we still need to check for forwards. Ugg. 344 | // clearInterval(this.locationTimer); 345 | handleBackButton(); 346 | } 347 | 348 | // first check to see if we could have gone forward. We always halt on 349 | // a no-hash item. 350 | if (forwardStack.length > 0) { 351 | if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { 352 | urlActions.forward = true; 353 | handleForwardButton(); 354 | } 355 | } 356 | 357 | // ok, that didn't work, try someplace back in the history stack 358 | if ((bsl >= 2) && (backStack[bsl - 2])) { 359 | if (backStack[bsl - 2].flexAppUrl == getHash()) { 360 | urlActions.back = true; 361 | handleBackButton(); 362 | } 363 | } 364 | 365 | if (!urlActions.back && !urlActions.forward) { 366 | var foundInStacks = { 367 | back: -1, 368 | forward: -1 369 | } 370 | 371 | for (var i = 0; i < backStack.length; i++) { 372 | if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { 373 | arbitraryUrl = true; 374 | foundInStacks.back = i; 375 | } 376 | } 377 | for (var i = 0; i < forwardStack.length; i++) { 378 | if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { 379 | arbitraryUrl = true; 380 | foundInStacks.forward = i; 381 | } 382 | } 383 | handleArbitraryUrl(); 384 | } 385 | 386 | // Firefox changed; do a callback into BrowserManager to tell it. 387 | currentHref = document.location.href; 388 | var flexAppUrl = getHash(); 389 | if (flexAppUrl == '') { 390 | //flexAppUrl = defaultHash; 391 | } 392 | //ADR: to fix multiple 393 | if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { 394 | var pl = getPlayers(); 395 | for (var i = 0; i < pl.length; i++) { 396 | pl[i].browserURLChange(flexAppUrl); 397 | } 398 | } else { 399 | getPlayer().browserURLChange(flexAppUrl); 400 | } 401 | } 402 | } 403 | //setTimeout(checkForUrlChange, 50); 404 | } 405 | 406 | /* Write an anchor into the page to legitimize it as a URL for Firefox et al. */ 407 | function addAnchor(flexAppUrl) 408 | { 409 | if (document.getElementsByName(flexAppUrl).length == 0) { 410 | getAnchorElement().innerHTML += "" + flexAppUrl + ""; 411 | } 412 | } 413 | 414 | var _initialize = function () { 415 | if (browser.ie) 416 | { 417 | var scripts = document.getElementsByTagName('script'); 418 | for (var i = 0, s; s = scripts[i]; i++) { 419 | if (s.src.indexOf("history.js") > -1) { 420 | var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); 421 | } 422 | } 423 | historyFrameSourcePrefix = iframe_location + "?"; 424 | var src = historyFrameSourcePrefix; 425 | 426 | var iframe = document.createElement("iframe"); 427 | iframe.id = 'ie_historyFrame'; 428 | iframe.name = 'ie_historyFrame'; 429 | //iframe.src = historyFrameSourcePrefix; 430 | try { 431 | document.body.appendChild(iframe); 432 | } catch(e) { 433 | setTimeout(function() { 434 | document.body.appendChild(iframe); 435 | }, 0); 436 | } 437 | } 438 | 439 | if (browser.safari) 440 | { 441 | var rememberDiv = document.createElement("div"); 442 | rememberDiv.id = 'safari_rememberDiv'; 443 | document.body.appendChild(rememberDiv); 444 | rememberDiv.innerHTML = ''; 445 | 446 | var formDiv = document.createElement("div"); 447 | formDiv.id = 'safari_formDiv'; 448 | document.body.appendChild(formDiv); 449 | 450 | var reloader_content = document.createElement('div'); 451 | reloader_content.id = 'safarireloader'; 452 | var scripts = document.getElementsByTagName('script'); 453 | for (var i = 0, s; s = scripts[i]; i++) { 454 | if (s.src.indexOf("history.js") > -1) { 455 | html = (new String(s.src)).replace(".js", ".html"); 456 | } 457 | } 458 | reloader_content.innerHTML = ''; 459 | document.body.appendChild(reloader_content); 460 | reloader_content.style.position = 'absolute'; 461 | reloader_content.style.left = reloader_content.style.top = '-9999px'; 462 | iframe = reloader_content.getElementsByTagName('iframe')[0]; 463 | 464 | if (document.getElementById("safari_remember_field").value != "" ) { 465 | historyHash = document.getElementById("safari_remember_field").value.split(","); 466 | } 467 | 468 | } 469 | 470 | if (browser.firefox) 471 | { 472 | var anchorDiv = document.createElement("div"); 473 | anchorDiv.id = 'firefox_anchorDiv'; 474 | document.body.appendChild(anchorDiv); 475 | } 476 | 477 | //setTimeout(checkForUrlChange, 50); 478 | } 479 | 480 | return { 481 | historyHash: historyHash, 482 | backStack: function() { return backStack; }, 483 | forwardStack: function() { return forwardStack }, 484 | getPlayer: getPlayer, 485 | initialize: function(src) { 486 | _initialize(src); 487 | }, 488 | setURL: function(url) { 489 | document.location.href = url; 490 | }, 491 | getURL: function() { 492 | return document.location.href; 493 | }, 494 | getTitle: function() { 495 | return document.title; 496 | }, 497 | setTitle: function(title) { 498 | try { 499 | backStack[backStack.length - 1].title = title; 500 | } catch(e) { } 501 | //if on safari, set the title to be the empty string. 502 | if (browser.safari) { 503 | if (title == "") { 504 | try { 505 | var tmp = window.location.href.toString(); 506 | title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); 507 | } catch(e) { 508 | title = ""; 509 | } 510 | } 511 | } 512 | document.title = title; 513 | }, 514 | setDefaultURL: function(def) 515 | { 516 | defaultHash = def; 517 | def = getHash(); 518 | //trailing ? is important else an extra frame gets added to the history 519 | //when navigating back to the first page. Alternatively could check 520 | //in history frame navigation to compare # and ?. 521 | if (browser.ie) 522 | { 523 | window['_ie_firstload'] = true; 524 | var sourceToSet = historyFrameSourcePrefix + def; 525 | var func = function() { 526 | getHistoryFrame().src = sourceToSet; 527 | window.location.replace("#" + def); 528 | setInterval(checkForUrlChange, 50); 529 | } 530 | try { 531 | func(); 532 | } catch(e) { 533 | window.setTimeout(function() { func(); }, 0); 534 | } 535 | } 536 | 537 | if (browser.safari) 538 | { 539 | currentHistoryLength = history.length; 540 | if (historyHash.length == 0) { 541 | historyHash[currentHistoryLength] = def; 542 | var newloc = "#" + def; 543 | window.location.replace(newloc); 544 | } else { 545 | //alert(historyHash[historyHash.length-1]); 546 | } 547 | //setHash(def); 548 | setInterval(checkForUrlChange, 50); 549 | } 550 | 551 | 552 | if (browser.firefox || browser.opera) 553 | { 554 | var reg = new RegExp("#" + def + "$"); 555 | if (window.location.toString().match(reg)) { 556 | } else { 557 | var newloc ="#" + def; 558 | window.location.replace(newloc); 559 | } 560 | setInterval(checkForUrlChange, 50); 561 | //setHash(def); 562 | } 563 | 564 | }, 565 | 566 | /* Set the current browser URL; called from inside BrowserManager to propagate 567 | * the application state out to the container. 568 | */ 569 | setBrowserURL: function(flexAppUrl, objectId) { 570 | if (browser.ie && typeof objectId != "undefined") { 571 | currentObjectId = objectId; 572 | } 573 | //fromIframe = fromIframe || false; 574 | //fromFlex = fromFlex || false; 575 | //alert("setBrowserURL: " + flexAppUrl); 576 | //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; 577 | 578 | var pos = document.location.href.indexOf('#'); 579 | var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; 580 | var newUrl = baseUrl + '#' + flexAppUrl; 581 | 582 | if (document.location.href != newUrl && document.location.href + '#' != newUrl) { 583 | currentHref = newUrl; 584 | addHistoryEntry(baseUrl, newUrl, flexAppUrl); 585 | currentHistoryLength = history.length; 586 | } 587 | 588 | return false; 589 | }, 590 | 591 | browserURLChange: function(flexAppUrl) { 592 | var objectId = null; 593 | if (browser.ie && currentObjectId != null) { 594 | objectId = currentObjectId; 595 | } 596 | pendingURL = ''; 597 | 598 | if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { 599 | var pl = getPlayers(); 600 | for (var i = 0; i < pl.length; i++) { 601 | try { 602 | pl[i].browserURLChange(flexAppUrl); 603 | } catch(e) { } 604 | } 605 | } else { 606 | try { 607 | getPlayer(objectId).browserURLChange(flexAppUrl); 608 | } catch(e) { } 609 | } 610 | 611 | currentObjectId = null; 612 | } 613 | 614 | } 615 | 616 | })(); 617 | 618 | // Initialization 619 | 620 | // Automated unit testing and other diagnostics 621 | 622 | function setURL(url) 623 | { 624 | document.location.href = url; 625 | } 626 | 627 | function backButton() 628 | { 629 | history.back(); 630 | } 631 | 632 | function forwardButton() 633 | { 634 | history.forward(); 635 | } 636 | 637 | function goForwardOrBackInHistory(step) 638 | { 639 | history.go(step); 640 | } 641 | 642 | //BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); 643 | (function(i) { 644 | var u =navigator.userAgent;var e=/*@cc_on!@*/false; 645 | var st = setTimeout; 646 | if(/webkit/i.test(u)){ 647 | st(function(){ 648 | var dr=document.readyState; 649 | if(dr=="loaded"||dr=="complete"){i()} 650 | else{st(arguments.callee,10);}},10); 651 | } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ 652 | document.addEventListener("DOMContentLoaded",i,false); 653 | } else if(e){ 654 | (function(){ 655 | var t=document.createElement('doc:rdy'); 656 | try{t.doScroll('left'); 657 | i();t=null; 658 | }catch(e){st(arguments.callee,0);}})(); 659 | } else{ 660 | window.onload=i; 661 | } 662 | })( function() {BrowserHistory.initialize();} ); 663 | -------------------------------------------------------------------------------- /html-template/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ${title} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 44 | 45 | 46 | 47 | 103 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /libs/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/libs/empty -------------------------------------------------------------------------------- /src/assets/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/bar.png -------------------------------------------------------------------------------- /src/assets/bt_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/bt_play.png -------------------------------------------------------------------------------- /src/assets/btn_cfgS_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_cfgS_down.png -------------------------------------------------------------------------------- /src/assets/btn_cfgS_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_cfgS_up.png -------------------------------------------------------------------------------- /src/assets/btn_cfg_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_cfg_down.png -------------------------------------------------------------------------------- /src/assets/btn_cfg_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_cfg_up.png -------------------------------------------------------------------------------- /src/assets/btn_dmk_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_dmk_down.png -------------------------------------------------------------------------------- /src/assets/btn_dmk_off_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_dmk_off_down.png -------------------------------------------------------------------------------- /src/assets/btn_dmk_off_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_dmk_off_up.png -------------------------------------------------------------------------------- /src/assets/btn_dmk_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_dmk_up.png -------------------------------------------------------------------------------- /src/assets/btn_fs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_fs_down.png -------------------------------------------------------------------------------- /src/assets/btn_fs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_fs_up.png -------------------------------------------------------------------------------- /src/assets/btn_pause_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_pause_down.png -------------------------------------------------------------------------------- /src/assets/btn_pause_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_pause_up.png -------------------------------------------------------------------------------- /src/assets/btn_play_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_play_down.png -------------------------------------------------------------------------------- /src/assets/btn_play_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_play_up.png -------------------------------------------------------------------------------- /src/assets/btn_sound_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_sound_down.png -------------------------------------------------------------------------------- /src/assets/btn_sound_mute_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_sound_mute_down.png -------------------------------------------------------------------------------- /src/assets/btn_sound_mute_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_sound_mute_up.png -------------------------------------------------------------------------------- /src/assets/btn_sound_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_sound_up.png -------------------------------------------------------------------------------- /src/assets/btn_ss_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_ss_down.png -------------------------------------------------------------------------------- /src/assets/btn_ss_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_ss_up.png -------------------------------------------------------------------------------- /src/assets/btn_stop_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_stop_down.png -------------------------------------------------------------------------------- /src/assets/btn_stop_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/btn_stop_up.png -------------------------------------------------------------------------------- /src/assets/trackerThumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/trackerThumb.png -------------------------------------------------------------------------------- /src/assets/trackerThumbDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabbany/ABPlayer/9bd87ae43901b024416e2ccab25169f3f48046dd/src/assets/trackerThumbDown.png -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSON.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * This class provides encoding and decoding of the JSON format. 38 | * 39 | * Example usage: 40 | * 41 | * // create a JSON string from an internal object 42 | * JSON.encode( myObject ); 43 | * 44 | * // read a JSON string into an internal object 45 | * var myObject:Object = JSON.decode( jsonString ); 46 | * 47 | */ 48 | public final class JSON 49 | { 50 | /** 51 | * Encodes a object into a JSON string. 52 | * 53 | * @param o The object to create a JSON string for 54 | * @return the JSON string representing o 55 | * @langversion ActionScript 3.0 56 | * @playerversion Flash 9.0 57 | * @tiptext 58 | */ 59 | public static function encode( o:Object ):String 60 | { 61 | return new JSONEncoder( o ).getString(); 62 | } 63 | 64 | /** 65 | * Decodes a JSON string into a native object. 66 | * 67 | * @param s The JSON string representing the object 68 | * @param strict Flag indicating if the decoder should strictly adhere 69 | * to the JSON standard or not. The default of true 70 | * throws errors if the format does not match the JSON syntax exactly. 71 | * Pass false to allow for non-properly-formatted JSON 72 | * strings to be decoded with more leniancy. 73 | * @return A native object as specified by s 74 | * @throw JSONParseError 75 | * @langversion ActionScript 3.0 76 | * @playerversion Flash 9.0 77 | * @tiptext 78 | */ 79 | public static function decode( s:String, strict:Boolean = true ):* 80 | { 81 | return new JSONDecoder( s, strict ).getValue(); 82 | } 83 | 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONDecoder.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public class JSONDecoder 37 | { 38 | 39 | /** 40 | * Flag indicating if the parser should be strict about the format 41 | * of the JSON string it is attempting to decode. 42 | */ 43 | private var strict:Boolean; 44 | 45 | /** The value that will get parsed from the JSON string */ 46 | private var value:*; 47 | 48 | /** The tokenizer designated to read the JSON string */ 49 | private var tokenizer:JSONTokenizer; 50 | 51 | /** The current token from the tokenizer */ 52 | private var token:JSONToken; 53 | 54 | /** 55 | * Constructs a new JSONDecoder to parse a JSON string 56 | * into a native object. 57 | * 58 | * @param s The JSON string to be converted 59 | * into a native object 60 | * @param strict Flag indicating if the JSON string needs to 61 | * strictly match the JSON standard or not. 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function JSONDecoder( s:String, strict:Boolean ) 67 | { 68 | this.strict = strict; 69 | tokenizer = new JSONTokenizer( s, strict ); 70 | 71 | nextToken(); 72 | value = parseValue(); 73 | 74 | // Make sure the input stream is empty 75 | if ( strict && nextToken() != null ) 76 | { 77 | tokenizer.parseError( "Unexpected characters left in input stream" ); 78 | } 79 | } 80 | 81 | /** 82 | * Gets the internal object that was created by parsing 83 | * the JSON string passed to the constructor. 84 | * 85 | * @return The internal object representation of the JSON 86 | * string that was passed to the constructor 87 | * @langversion ActionScript 3.0 88 | * @playerversion Flash 9.0 89 | * @tiptext 90 | */ 91 | public function getValue():* 92 | { 93 | return value; 94 | } 95 | 96 | /** 97 | * Returns the next token from the tokenzier reading 98 | * the JSON string 99 | */ 100 | private final function nextToken():JSONToken 101 | { 102 | return token = tokenizer.getNextToken(); 103 | } 104 | 105 | /** 106 | * Returns the next token from the tokenizer reading 107 | * the JSON string and verifies that the token is valid. 108 | */ 109 | private final function nextValidToken():JSONToken 110 | { 111 | token = tokenizer.getNextToken(); 112 | checkValidToken(); 113 | 114 | return token; 115 | } 116 | 117 | /** 118 | * Verifies that the token is valid. 119 | */ 120 | private final function checkValidToken():void 121 | { 122 | // Catch errors when the input stream ends abruptly 123 | if ( token == null ) 124 | { 125 | tokenizer.parseError( "Unexpected end of input" ); 126 | } 127 | } 128 | 129 | /** 130 | * Attempt to parse an array. 131 | */ 132 | private final function parseArray():Array 133 | { 134 | // create an array internally that we're going to attempt 135 | // to parse from the tokenizer 136 | var a:Array = new Array(); 137 | 138 | // grab the next token from the tokenizer to move 139 | // past the opening [ 140 | nextValidToken(); 141 | 142 | // check to see if we have an empty array 143 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 144 | { 145 | // we're done reading the array, so return it 146 | return a; 147 | } 148 | // in non-strict mode an empty array is also a comma 149 | // followed by a right bracket 150 | else if ( !strict && token.type == JSONTokenType.COMMA ) 151 | { 152 | // move past the comma 153 | nextValidToken(); 154 | 155 | // check to see if we're reached the end of the array 156 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 157 | { 158 | return a; 159 | } 160 | else 161 | { 162 | tokenizer.parseError( "Leading commas are not supported. Expecting ']' but found " + token.value ); 163 | } 164 | } 165 | 166 | // deal with elements of the array, and use an "infinite" 167 | // loop because we could have any amount of elements 168 | while ( true ) 169 | { 170 | // read in the value and add it to the array 171 | a.push( parseValue() ); 172 | 173 | // after the value there should be a ] or a , 174 | nextValidToken(); 175 | 176 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 177 | { 178 | // we're done reading the array, so return it 179 | return a; 180 | } 181 | else if ( token.type == JSONTokenType.COMMA ) 182 | { 183 | // move past the comma and read another value 184 | nextToken(); 185 | 186 | // Allow arrays to have a comma after the last element 187 | // if the decoder is not in strict mode 188 | if ( !strict ) 189 | { 190 | checkValidToken(); 191 | 192 | // Reached ",]" as the end of the array, so return it 193 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 194 | { 195 | return a; 196 | } 197 | } 198 | } 199 | else 200 | { 201 | tokenizer.parseError( "Expecting ] or , but found " + token.value ); 202 | } 203 | } 204 | 205 | return null; 206 | } 207 | 208 | /** 209 | * Attempt to parse an object. 210 | */ 211 | private final function parseObject():Object 212 | { 213 | // create the object internally that we're going to 214 | // attempt to parse from the tokenizer 215 | var o:Object = new Object(); 216 | 217 | // store the string part of an object member so 218 | // that we can assign it a value in the object 219 | var key:String 220 | 221 | // grab the next token from the tokenizer 222 | nextValidToken(); 223 | 224 | // check to see if we have an empty object 225 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 226 | { 227 | // we're done reading the object, so return it 228 | return o; 229 | } 230 | // in non-strict mode an empty object is also a comma 231 | // followed by a right bracket 232 | else if ( !strict && token.type == JSONTokenType.COMMA ) 233 | { 234 | // move past the comma 235 | nextValidToken(); 236 | 237 | // check to see if we're reached the end of the object 238 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 239 | { 240 | return o; 241 | } 242 | else 243 | { 244 | tokenizer.parseError( "Leading commas are not supported. Expecting '}' but found " + token.value ); 245 | } 246 | } 247 | 248 | // deal with members of the object, and use an "infinite" 249 | // loop because we could have any amount of members 250 | while ( true ) 251 | { 252 | if ( token.type == JSONTokenType.STRING ) 253 | { 254 | // the string value we read is the key for the object 255 | key = String( token.value ); 256 | 257 | // move past the string to see what's next 258 | nextValidToken(); 259 | 260 | // after the string there should be a : 261 | if ( token.type == JSONTokenType.COLON ) 262 | { 263 | // move past the : and read/assign a value for the key 264 | nextToken(); 265 | o[ key ] = parseValue(); 266 | 267 | // move past the value to see what's next 268 | nextValidToken(); 269 | 270 | // after the value there's either a } or a , 271 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 272 | { 273 | // we're done reading the object, so return it 274 | return o; 275 | } 276 | else if ( token.type == JSONTokenType.COMMA ) 277 | { 278 | // skip past the comma and read another member 279 | nextToken(); 280 | 281 | // Allow objects to have a comma after the last member 282 | // if the decoder is not in strict mode 283 | if ( !strict ) 284 | { 285 | checkValidToken(); 286 | 287 | // Reached ",}" as the end of the object, so return it 288 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 289 | { 290 | return o; 291 | } 292 | } 293 | } 294 | else 295 | { 296 | tokenizer.parseError( "Expecting } or , but found " + token.value ); 297 | } 298 | } 299 | else 300 | { 301 | tokenizer.parseError( "Expecting : but found " + token.value ); 302 | } 303 | } 304 | else 305 | { 306 | tokenizer.parseError( "Expecting string but found " + token.value ); 307 | } 308 | } 309 | return null; 310 | } 311 | 312 | /** 313 | * Attempt to parse a value 314 | */ 315 | private final function parseValue():Object 316 | { 317 | checkValidToken(); 318 | 319 | switch ( token.type ) 320 | { 321 | case JSONTokenType.LEFT_BRACE: 322 | return parseObject(); 323 | 324 | case JSONTokenType.LEFT_BRACKET: 325 | return parseArray(); 326 | 327 | case JSONTokenType.STRING: 328 | case JSONTokenType.NUMBER: 329 | case JSONTokenType.TRUE: 330 | case JSONTokenType.FALSE: 331 | case JSONTokenType.NULL: 332 | return token.value; 333 | 334 | case JSONTokenType.NAN: 335 | if ( !strict ) 336 | { 337 | return token.value; 338 | } 339 | else 340 | { 341 | tokenizer.parseError( "Unexpected " + token.value ); 342 | } 343 | 344 | default: 345 | tokenizer.parseError( "Unexpected " + token.value ); 346 | 347 | } 348 | 349 | return null; 350 | } 351 | } 352 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONEncoder.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | import flash.utils.describeType; 37 | 38 | public class JSONEncoder 39 | { 40 | 41 | /** The string that is going to represent the object we're encoding */ 42 | private var jsonString:String; 43 | 44 | /** 45 | * Creates a new JSONEncoder. 46 | * 47 | * @param o The object to encode as a JSON string 48 | * @langversion ActionScript 3.0 49 | * @playerversion Flash 9.0 50 | * @tiptext 51 | */ 52 | public function JSONEncoder( value:* ) 53 | { 54 | jsonString = convertToString( value ); 55 | } 56 | 57 | /** 58 | * Gets the JSON string from the encoder. 59 | * 60 | * @return The JSON string representation of the object 61 | * that was passed to the constructor 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function getString():String 67 | { 68 | return jsonString; 69 | } 70 | 71 | /** 72 | * Converts a value to it's JSON string equivalent. 73 | * 74 | * @param value The value to convert. Could be any 75 | * type (object, number, array, etc) 76 | */ 77 | private function convertToString( value:* ):String 78 | { 79 | // determine what value is and convert it based on it's type 80 | if ( value is String ) 81 | { 82 | // escape the string so it's formatted correctly 83 | return escapeString( value as String ); 84 | } 85 | else if ( value is Number ) 86 | { 87 | // only encode numbers that finate 88 | return isFinite( value as Number ) ? value.toString() : "null"; 89 | } 90 | else if ( value is Boolean ) 91 | { 92 | // convert boolean to string easily 93 | return value ? "true" : "false"; 94 | } 95 | else if ( value is Array ) 96 | { 97 | // call the helper method to convert an array 98 | return arrayToString( value as Array ); 99 | } 100 | else if ( value is Object && value != null ) 101 | { 102 | // call the helper method to convert an object 103 | return objectToString( value ); 104 | } 105 | 106 | return "null"; 107 | } 108 | 109 | /** 110 | * Escapes a string accoding to the JSON specification. 111 | * 112 | * @param str The string to be escaped 113 | * @return The string with escaped special characters 114 | * according to the JSON specification 115 | */ 116 | private function escapeString( str:String ):String 117 | { 118 | // create a string to store the string's jsonstring value 119 | var s:String = ""; 120 | // current character in the string we're processing 121 | var ch:String; 122 | // store the length in a local variable to reduce lookups 123 | var len:Number = str.length; 124 | 125 | // loop over all of the characters in the string 126 | for ( var i:int = 0; i < len; i++ ) 127 | { 128 | // examine the character to determine if we have to escape it 129 | ch = str.charAt( i ); 130 | switch ( ch ) 131 | { 132 | case '"': // quotation mark 133 | s += "\\\""; 134 | break; 135 | 136 | //case '/': // solidus 137 | // s += "\\/"; 138 | // break; 139 | 140 | case '\\': // reverse solidus 141 | s += "\\\\"; 142 | break; 143 | 144 | case '\b': // bell 145 | s += "\\b"; 146 | break; 147 | 148 | case '\f': // form feed 149 | s += "\\f"; 150 | break; 151 | 152 | case '\n': // newline 153 | s += "\\n"; 154 | break; 155 | 156 | case '\r': // carriage return 157 | s += "\\r"; 158 | break; 159 | 160 | case '\t': // horizontal tab 161 | s += "\\t"; 162 | break; 163 | 164 | default: // everything else 165 | 166 | // check for a control character and escape as unicode 167 | if ( ch < ' ' ) 168 | { 169 | // get the hex digit(s) of the character (either 1 or 2 digits) 170 | var hexCode:String = ch.charCodeAt( 0 ).toString( 16 ); 171 | 172 | // ensure that there are 4 digits by adjusting 173 | // the # of zeros accordingly. 174 | var zeroPad:String = hexCode.length == 2 ? "00" : "000"; 175 | 176 | // create the unicode escape sequence with 4 hex digits 177 | s += "\\u" + zeroPad + hexCode; 178 | } 179 | else 180 | { 181 | 182 | // no need to do any special encoding, just pass-through 183 | s += ch; 184 | 185 | } 186 | } // end switch 187 | 188 | } // end for loop 189 | 190 | return "\"" + s + "\""; 191 | } 192 | 193 | /** 194 | * Converts an array to it's JSON string equivalent 195 | * 196 | * @param a The array to convert 197 | * @return The JSON string representation of a 198 | */ 199 | private function arrayToString( a:Array ):String 200 | { 201 | // create a string to store the array's jsonstring value 202 | var s:String = ""; 203 | 204 | // loop over the elements in the array and add their converted 205 | // values to the string 206 | var length:int = a.length; 207 | for ( var i:int = 0; i < length; i++ ) 208 | { 209 | // when the length is 0 we're adding the first element so 210 | // no comma is necessary 211 | if ( s.length > 0 ) 212 | { 213 | // we've already added an element, so add the comma separator 214 | s += "," 215 | } 216 | 217 | // convert the value to a string 218 | s += convertToString( a[ i ] ); 219 | } 220 | 221 | // KNOWN ISSUE: In ActionScript, Arrays can also be associative 222 | // objects and you can put anything in them, ie: 223 | // myArray["foo"] = "bar"; 224 | // 225 | // These properties aren't picked up in the for loop above because 226 | // the properties don't correspond to indexes. However, we're 227 | // sort of out luck because the JSON specification doesn't allow 228 | // these types of array properties. 229 | // 230 | // So, if the array was also used as an associative object, there 231 | // may be some values in the array that don't get properly encoded. 232 | // 233 | // A possible solution is to instead encode the Array as an Object 234 | // but then it won't get decoded correctly (and won't be an 235 | // Array instance) 236 | 237 | // close the array and return it's string value 238 | return "[" + s + "]"; 239 | } 240 | 241 | /** 242 | * Converts an object to it's JSON string equivalent 243 | * 244 | * @param o The object to convert 245 | * @return The JSON string representation of o 246 | */ 247 | private function objectToString( o:Object ):String 248 | { 249 | // create a string to store the object's jsonstring value 250 | var s:String = ""; 251 | 252 | // determine if o is a class instance or a plain object 253 | var classInfo:XML = describeType( o ); 254 | if ( classInfo.@name.toString() == "Object" ) 255 | { 256 | // the value of o[key] in the loop below - store this 257 | // as a variable so we don't have to keep looking up o[key] 258 | // when testing for valid values to convert 259 | var value:Object; 260 | 261 | // loop over the keys in the object and add their converted 262 | // values to the string 263 | for ( var key:String in o ) 264 | { 265 | // assign value to a variable for quick lookup 266 | value = o[ key ]; 267 | 268 | // don't add function's to the JSON string 269 | if ( value is Function ) 270 | { 271 | // skip this key and try another 272 | continue; 273 | } 274 | 275 | // when the length is 0 we're adding the first item so 276 | // no comma is necessary 277 | if ( s.length > 0 ) 278 | { 279 | // we've already added an item, so add the comma separator 280 | s += "," 281 | } 282 | 283 | s += escapeString( key ) + ":" + convertToString( value ); 284 | } 285 | } 286 | else // o is a class instance 287 | { 288 | // Loop over all of the variables and accessors in the class and 289 | // serialize them along with their values. 290 | for each ( var v:XML in classInfo..*.( 291 | name() == "variable" 292 | || 293 | ( 294 | name() == "accessor" 295 | // Issue #116 - Make sure accessors are readable 296 | && attribute( "access" ).charAt( 0 ) == "r" ) 297 | ) ) 298 | { 299 | // Issue #110 - If [Transient] metadata exists, then we should skip 300 | if ( v.metadata && v.metadata.( @name == "Transient" ).length() > 0 ) 301 | { 302 | continue; 303 | } 304 | 305 | // When the length is 0 we're adding the first item so 306 | // no comma is necessary 307 | if ( s.length > 0 ) 308 | { 309 | // We've already added an item, so add the comma separator 310 | s += "," 311 | } 312 | 313 | s += escapeString( v.@name.toString() ) + ":" 314 | + convertToString( o[ v.@name ] ); 315 | } 316 | } 317 | 318 | return "{" + s + "}"; 319 | } 320 | 321 | } 322 | 323 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONParseError.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * 38 | * 39 | */ 40 | public class JSONParseError extends Error 41 | { 42 | 43 | /** The location in the string where the error occurred */ 44 | private var _location:int; 45 | 46 | /** The string in which the parse error occurred */ 47 | private var _text:String; 48 | 49 | /** 50 | * Constructs a new JSONParseError. 51 | * 52 | * @param message The error message that occured during parsing 53 | * @langversion ActionScript 3.0 54 | * @playerversion Flash 9.0 55 | * @tiptext 56 | */ 57 | public function JSONParseError( message:String = "", location:int = 0, text:String = "" ) 58 | { 59 | super( message ); 60 | name = "JSONParseError"; 61 | _location = location; 62 | _text = text; 63 | } 64 | 65 | /** 66 | * Provides read-only access to the location variable. 67 | * 68 | * @return The location in the string where the error occurred 69 | * @langversion ActionScript 3.0 70 | * @playerversion Flash 9.0 71 | * @tiptext 72 | */ 73 | public function get location():int 74 | { 75 | return _location; 76 | } 77 | 78 | /** 79 | * Provides read-only access to the text variable. 80 | * 81 | * @return The string in which the error occurred 82 | * @langversion ActionScript 3.0 83 | * @playerversion Flash 9.0 84 | * @tiptext 85 | */ 86 | public function get text():String 87 | { 88 | return _text; 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONToken.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public final class JSONToken 37 | { 38 | 39 | /** 40 | * The type of the token. 41 | * 42 | * @langversion ActionScript 3.0 43 | * @playerversion Flash 9.0 44 | * @tiptext 45 | */ 46 | public var type:int; 47 | 48 | /** 49 | * The value of the token 50 | * 51 | * @langversion ActionScript 3.0 52 | * @playerversion Flash 9.0 53 | * @tiptext 54 | */ 55 | public var value:Object; 56 | 57 | /** 58 | * Creates a new JSONToken with a specific token type and value. 59 | * 60 | * @param type The JSONTokenType of the token 61 | * @param value The value of the token 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function JSONToken( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ) 67 | { 68 | this.type = type; 69 | this.value = value; 70 | } 71 | 72 | /** 73 | * Reusable token instance. 74 | * 75 | * @see #create() 76 | */ 77 | internal static const token:JSONToken = new JSONToken(); 78 | 79 | /** 80 | * Factory method to create instances. Because we don't need more than one instance 81 | * of a token at a time, we can always use the same instance to improve performance 82 | * and reduce memory consumption during decoding. 83 | */ 84 | internal static function create( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ):JSONToken 85 | { 86 | token.type = type; 87 | token.value = value; 88 | 89 | return token; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONTokenType.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * Class containing constant values for the different types 38 | * of tokens in a JSON encoded string. 39 | */ 40 | public final class JSONTokenType 41 | { 42 | public static const UNKNOWN:int = -1; 43 | 44 | public static const COMMA:int = 0; 45 | 46 | public static const LEFT_BRACE:int = 1; 47 | 48 | public static const RIGHT_BRACE:int = 2; 49 | 50 | public static const LEFT_BRACKET:int = 3; 51 | 52 | public static const RIGHT_BRACKET:int = 4; 53 | 54 | public static const COLON:int = 6; 55 | 56 | public static const TRUE:int = 7; 57 | 58 | public static const FALSE:int = 8; 59 | 60 | public static const NULL:int = 9; 61 | 62 | public static const STRING:int = 10; 63 | 64 | public static const NUMBER:int = 11; 65 | 66 | public static const NAN:int = 12; 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONTokenizer.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public class JSONTokenizer 37 | { 38 | 39 | /** 40 | * Flag indicating if the tokenizer should only recognize 41 | * standard JSON tokens. Setting to false allows 42 | * tokens such as NaN and allows numbers to be formatted as 43 | * hex, etc. 44 | */ 45 | private var strict:Boolean; 46 | 47 | /** The object that will get parsed from the JSON string */ 48 | private var obj:Object; 49 | 50 | /** The JSON string to be parsed */ 51 | private var jsonString:String; 52 | 53 | /** The current parsing location in the JSON string */ 54 | private var loc:int; 55 | 56 | /** The current character in the JSON string during parsing */ 57 | private var ch:String; 58 | 59 | /** 60 | * The regular expression used to make sure the string does not 61 | * contain invalid control characters. 62 | */ 63 | private const controlCharsRegExp:RegExp = /[\x00-\x1F]/; 64 | 65 | /** 66 | * Constructs a new JSONDecoder to parse a JSON string 67 | * into a native object. 68 | * 69 | * @param s The JSON string to be converted 70 | * into a native object 71 | */ 72 | public function JSONTokenizer( s:String, strict:Boolean ) 73 | { 74 | jsonString = s; 75 | this.strict = strict; 76 | loc = 0; 77 | 78 | // prime the pump by getting the first character 79 | nextChar(); 80 | } 81 | 82 | /** 83 | * Gets the next token in the input sting and advances 84 | * the character to the next character after the token 85 | */ 86 | public function getNextToken():JSONToken 87 | { 88 | var token:JSONToken = null; 89 | 90 | // skip any whitespace / comments since the last 91 | // token was read 92 | skipIgnored(); 93 | 94 | // examine the new character and see what we have... 95 | switch ( ch ) 96 | { 97 | case '{': 98 | token = JSONToken.create( JSONTokenType.LEFT_BRACE, ch ); 99 | nextChar(); 100 | break 101 | 102 | case '}': 103 | token = JSONToken.create( JSONTokenType.RIGHT_BRACE, ch ); 104 | nextChar(); 105 | break 106 | 107 | case '[': 108 | token = JSONToken.create( JSONTokenType.LEFT_BRACKET, ch ); 109 | nextChar(); 110 | break 111 | 112 | case ']': 113 | token = JSONToken.create( JSONTokenType.RIGHT_BRACKET, ch ); 114 | nextChar(); 115 | break 116 | 117 | case ',': 118 | token = JSONToken.create( JSONTokenType.COMMA, ch ); 119 | nextChar(); 120 | break 121 | 122 | case ':': 123 | token = JSONToken.create( JSONTokenType.COLON, ch ); 124 | nextChar(); 125 | break; 126 | 127 | case 't': // attempt to read true 128 | var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); 129 | 130 | if ( possibleTrue == "true" ) 131 | { 132 | token = JSONToken.create( JSONTokenType.TRUE, true ); 133 | nextChar(); 134 | } 135 | else 136 | { 137 | parseError( "Expecting 'true' but found " + possibleTrue ); 138 | } 139 | 140 | break; 141 | 142 | case 'f': // attempt to read false 143 | var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); 144 | 145 | if ( possibleFalse == "false" ) 146 | { 147 | token = JSONToken.create( JSONTokenType.FALSE, false ); 148 | nextChar(); 149 | } 150 | else 151 | { 152 | parseError( "Expecting 'false' but found " + possibleFalse ); 153 | } 154 | 155 | break; 156 | 157 | case 'n': // attempt to read null 158 | var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); 159 | 160 | if ( possibleNull == "null" ) 161 | { 162 | token = JSONToken.create( JSONTokenType.NULL, null ); 163 | nextChar(); 164 | } 165 | else 166 | { 167 | parseError( "Expecting 'null' but found " + possibleNull ); 168 | } 169 | 170 | break; 171 | 172 | case 'N': // attempt to read NaN 173 | var possibleNaN:String = "N" + nextChar() + nextChar(); 174 | 175 | if ( possibleNaN == "NaN" ) 176 | { 177 | token = JSONToken.create( JSONTokenType.NAN, NaN ); 178 | nextChar(); 179 | } 180 | else 181 | { 182 | parseError( "Expecting 'NaN' but found " + possibleNaN ); 183 | } 184 | 185 | break; 186 | 187 | case '"': // the start of a string 188 | token = readString(); 189 | break; 190 | 191 | default: 192 | // see if we can read a number 193 | if ( isDigit( ch ) || ch == '-' ) 194 | { 195 | token = readNumber(); 196 | } 197 | else if ( ch == '' ) 198 | { 199 | // check for reading past the end of the string 200 | token = null; 201 | } 202 | else 203 | { 204 | // not sure what was in the input string - it's not 205 | // anything we expected 206 | parseError( "Unexpected " + ch + " encountered" ); 207 | } 208 | } 209 | 210 | return token; 211 | } 212 | 213 | /** 214 | * Attempts to read a string from the input string. Places 215 | * the character location at the first character after the 216 | * string. It is assumed that ch is " before this method is called. 217 | * 218 | * @return the JSONToken with the string value if a string could 219 | * be read. Throws an error otherwise. 220 | */ 221 | private final function readString():JSONToken 222 | { 223 | // Rather than examine the string character-by-character, it's 224 | // faster to use indexOf to try to and find the closing quote character 225 | // and then replace escape sequences after the fact. 226 | 227 | // Start at the current input stream position 228 | var quoteIndex:int = loc; 229 | do 230 | { 231 | // Find the next quote in the input stream 232 | quoteIndex = jsonString.indexOf( "\"", quoteIndex ); 233 | 234 | if ( quoteIndex >= 0 ) 235 | { 236 | // We found the next double quote character in the string, but we need 237 | // to make sure it is not part of an escape sequence. 238 | 239 | // Keep looping backwards while the previous character is a backslash 240 | var backspaceCount:int = 0; 241 | var backspaceIndex:int = quoteIndex - 1; 242 | while ( jsonString.charAt( backspaceIndex ) == "\\" ) 243 | { 244 | backspaceCount++; 245 | backspaceIndex--; 246 | } 247 | 248 | // If we have an even number of backslashes, that means this is the ending quote 249 | if ( ( backspaceCount & 1 ) == 0 ) 250 | { 251 | break; 252 | } 253 | 254 | // At this point, the quote was determined to be part of an escape sequence 255 | // so we need to move past the quote index to look for the next one 256 | quoteIndex++; 257 | } 258 | else // There are no more quotes in the string and we haven't found the end yet 259 | { 260 | parseError( "Unterminated string literal" ); 261 | } 262 | } while ( true ); 263 | 264 | // Unescape the string 265 | // the token for the string we'll try to read 266 | var token:JSONToken = JSONToken.create( 267 | JSONTokenType.STRING, 268 | // Attach resulting string to the token to return it 269 | unescapeString( jsonString.substr( loc, quoteIndex - loc ) ) ); 270 | 271 | // Move past the closing quote in the input string. This updates the next 272 | // character in the input stream to be the character one after the closing quote 273 | loc = quoteIndex + 1; 274 | nextChar(); 275 | 276 | return token; 277 | } 278 | 279 | /** 280 | * Convert all JavaScript escape characters into normal characters 281 | * 282 | * @param input The input string to convert 283 | * @return Original string with escape characters replaced by real characters 284 | */ 285 | public function unescapeString( input:String ):String 286 | { 287 | // Issue #104 - If the string contains any unescaped control characters, this 288 | // is an error in strict mode 289 | if ( strict && controlCharsRegExp.test( input ) ) 290 | { 291 | parseError( "String contains unescaped control character (0x00-0x1F)" ); 292 | } 293 | 294 | var result:String = ""; 295 | var backslashIndex:int = 0; 296 | var nextSubstringStartPosition:int = 0; 297 | var len:int = input.length; 298 | do 299 | { 300 | // Find the next backslash in the input 301 | backslashIndex = input.indexOf( '\\', nextSubstringStartPosition ); 302 | 303 | if ( backslashIndex >= 0 ) 304 | { 305 | result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition ); 306 | 307 | // Move past the backslash and next character (all escape sequences are 308 | // two characters, except for \u, which will advance this further) 309 | nextSubstringStartPosition = backslashIndex + 2; 310 | 311 | // Check the next character so we know what to escape 312 | var escapedChar:String = input.charAt( backslashIndex + 1 ); 313 | switch ( escapedChar ) 314 | { 315 | // Try to list the most common expected cases first to improve performance 316 | 317 | case '"': 318 | result += escapedChar; 319 | break; // quotation mark 320 | case '\\': 321 | result += escapedChar; 322 | break; // reverse solidus 323 | case 'n': 324 | result += '\n'; 325 | break; // newline 326 | case 'r': 327 | result += '\r'; 328 | break; // carriage return 329 | case 't': 330 | result += '\t'; 331 | break; // horizontal tab 332 | 333 | // Convert a unicode escape sequence to it's character value 334 | case 'u': 335 | 336 | // Save the characters as a string we'll convert to an int 337 | var hexValue:String = ""; 338 | 339 | var unicodeEndPosition:int = nextSubstringStartPosition + 4; 340 | 341 | // Make sure there are enough characters in the string leftover 342 | if ( unicodeEndPosition > len ) 343 | { 344 | parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." ); 345 | } 346 | 347 | // Try to find 4 hex characters 348 | for ( var i:int = nextSubstringStartPosition; i < unicodeEndPosition; i++ ) 349 | { 350 | // get the next character and determine 351 | // if it's a valid hex digit or not 352 | var possibleHexChar:String = input.charAt( i ); 353 | if ( !isHexDigit( possibleHexChar ) ) 354 | { 355 | parseError( "Excepted a hex digit, but found: " + possibleHexChar ); 356 | } 357 | 358 | // Valid hex digit, add it to the value 359 | hexValue += possibleHexChar; 360 | } 361 | 362 | // Convert hexValue to an integer, and use that 363 | // integer value to create a character to add 364 | // to our string. 365 | result += String.fromCharCode( parseInt( hexValue, 16 ) ); 366 | 367 | // Move past the 4 hex digits that we just read 368 | nextSubstringStartPosition = unicodeEndPosition; 369 | break; 370 | 371 | case 'f': 372 | result += '\f'; 373 | break; // form feed 374 | case '/': 375 | result += '/'; 376 | break; // solidus 377 | case 'b': 378 | result += '\b'; 379 | break; // bell 380 | default: 381 | result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through 382 | } 383 | } 384 | else 385 | { 386 | // No more backslashes to replace, append the rest of the string 387 | result += input.substr( nextSubstringStartPosition ); 388 | break; 389 | } 390 | 391 | } while ( nextSubstringStartPosition < len ); 392 | 393 | return result; 394 | } 395 | 396 | /** 397 | * Attempts to read a number from the input string. Places 398 | * the character location at the first character after the 399 | * number. 400 | * 401 | * @return The JSONToken with the number value if a number could 402 | * be read. Throws an error otherwise. 403 | */ 404 | private final function readNumber():JSONToken 405 | { 406 | // the string to accumulate the number characters 407 | // into that we'll convert to a number at the end 408 | var input:String = ""; 409 | 410 | // check for a negative number 411 | if ( ch == '-' ) 412 | { 413 | input += '-'; 414 | nextChar(); 415 | } 416 | 417 | // the number must start with a digit 418 | if ( !isDigit( ch ) ) 419 | { 420 | parseError( "Expecting a digit" ); 421 | } 422 | 423 | // 0 can only be the first digit if it 424 | // is followed by a decimal point 425 | if ( ch == '0' ) 426 | { 427 | input += ch; 428 | nextChar(); 429 | 430 | // make sure no other digits come after 0 431 | if ( isDigit( ch ) ) 432 | { 433 | parseError( "A digit cannot immediately follow 0" ); 434 | } 435 | // unless we have 0x which starts a hex number, but this 436 | // doesn't match JSON spec so check for not strict mode. 437 | else if ( !strict && ch == 'x' ) 438 | { 439 | // include the x in the input 440 | input += ch; 441 | nextChar(); 442 | 443 | // need at least one hex digit after 0x to 444 | // be valid 445 | if ( isHexDigit( ch ) ) 446 | { 447 | input += ch; 448 | nextChar(); 449 | } 450 | else 451 | { 452 | parseError( "Number in hex format require at least one hex digit after \"0x\"" ); 453 | } 454 | 455 | // consume all of the hex values 456 | while ( isHexDigit( ch ) ) 457 | { 458 | input += ch; 459 | nextChar(); 460 | } 461 | } 462 | } 463 | else 464 | { 465 | // read numbers while we can 466 | while ( isDigit( ch ) ) 467 | { 468 | input += ch; 469 | nextChar(); 470 | } 471 | } 472 | 473 | // check for a decimal value 474 | if ( ch == '.' ) 475 | { 476 | input += '.'; 477 | nextChar(); 478 | 479 | // after the decimal there has to be a digit 480 | if ( !isDigit( ch ) ) 481 | { 482 | parseError( "Expecting a digit" ); 483 | } 484 | 485 | // read more numbers to get the decimal value 486 | while ( isDigit( ch ) ) 487 | { 488 | input += ch; 489 | nextChar(); 490 | } 491 | } 492 | 493 | // check for scientific notation 494 | if ( ch == 'e' || ch == 'E' ) 495 | { 496 | input += "e" 497 | nextChar(); 498 | // check for sign 499 | if ( ch == '+' || ch == '-' ) 500 | { 501 | input += ch; 502 | nextChar(); 503 | } 504 | 505 | // require at least one number for the exponent 506 | // in this case 507 | if ( !isDigit( ch ) ) 508 | { 509 | parseError( "Scientific notation number needs exponent value" ); 510 | } 511 | 512 | // read in the exponent 513 | while ( isDigit( ch ) ) 514 | { 515 | input += ch; 516 | nextChar(); 517 | } 518 | } 519 | 520 | // convert the string to a number value 521 | var num:Number = Number( input ); 522 | 523 | if ( isFinite( num ) && !isNaN( num ) ) 524 | { 525 | // the token for the number that we've read 526 | return JSONToken.create( JSONTokenType.NUMBER, num ); 527 | } 528 | else 529 | { 530 | parseError( "Number " + num + " is not valid!" ); 531 | } 532 | 533 | return null; 534 | } 535 | 536 | /** 537 | * Reads the next character in the input 538 | * string and advances the character location. 539 | * 540 | * @return The next character in the input string, or 541 | * null if we've read past the end. 542 | */ 543 | private final function nextChar():String 544 | { 545 | return ch = jsonString.charAt( loc++ ); 546 | } 547 | 548 | /** 549 | * Advances the character location past any 550 | * sort of white space and comments 551 | */ 552 | private final function skipIgnored():void 553 | { 554 | var originalLoc:int; 555 | 556 | // keep trying to skip whitespace and comments as long 557 | // as we keep advancing past the original location 558 | do 559 | { 560 | originalLoc = loc; 561 | skipWhite(); 562 | skipComments(); 563 | } while ( originalLoc != loc ); 564 | } 565 | 566 | /** 567 | * Skips comments in the input string, either 568 | * single-line or multi-line. Advances the character 569 | * to the first position after the end of the comment. 570 | */ 571 | private function skipComments():void 572 | { 573 | if ( ch == '/' ) 574 | { 575 | // Advance past the first / to find out what type of comment 576 | nextChar(); 577 | switch ( ch ) 578 | { 579 | case '/': // single-line comment, read through end of line 580 | 581 | // Loop over the characters until we find 582 | // a newline or until there's no more characters left 583 | do 584 | { 585 | nextChar(); 586 | } while ( ch != '\n' && ch != '' ) 587 | 588 | // move past the \n 589 | nextChar(); 590 | 591 | break; 592 | 593 | case '*': // multi-line comment, read until closing */ 594 | 595 | // move past the opening * 596 | nextChar(); 597 | 598 | // try to find a trailing */ 599 | while ( true ) 600 | { 601 | if ( ch == '*' ) 602 | { 603 | // check to see if we have a closing / 604 | nextChar(); 605 | if ( ch == '/' ) 606 | { 607 | // move past the end of the closing */ 608 | nextChar(); 609 | break; 610 | } 611 | } 612 | else 613 | { 614 | // move along, looking if the next character is a * 615 | nextChar(); 616 | } 617 | 618 | // when we're here we've read past the end of 619 | // the string without finding a closing */, so error 620 | if ( ch == '' ) 621 | { 622 | parseError( "Multi-line comment not closed" ); 623 | } 624 | } 625 | 626 | break; 627 | 628 | // Can't match a comment after a /, so it's a parsing error 629 | default: 630 | parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); 631 | } 632 | } 633 | 634 | } 635 | 636 | 637 | /** 638 | * Skip any whitespace in the input string and advances 639 | * the character to the first character after any possible 640 | * whitespace. 641 | */ 642 | private final function skipWhite():void 643 | { 644 | // As long as there are spaces in the input 645 | // stream, advance the current location pointer 646 | // past them 647 | while ( isWhiteSpace( ch ) ) 648 | { 649 | nextChar(); 650 | } 651 | 652 | } 653 | 654 | /** 655 | * Determines if a character is whitespace or not. 656 | * 657 | * @return True if the character passed in is a whitespace 658 | * character 659 | */ 660 | private final function isWhiteSpace( ch:String ):Boolean 661 | { 662 | // Check for the whitespace defined in the spec 663 | if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ) 664 | { 665 | return true; 666 | } 667 | // If we're not in strict mode, we also accept non-breaking space 668 | else if ( !strict && ch.charCodeAt( 0 ) == 160 ) 669 | { 670 | return true; 671 | } 672 | 673 | return false; 674 | } 675 | 676 | /** 677 | * Determines if a character is a digit [0-9]. 678 | * 679 | * @return True if the character passed in is a digit 680 | */ 681 | private final function isDigit( ch:String ):Boolean 682 | { 683 | return ( ch >= '0' && ch <= '9' ); 684 | } 685 | 686 | /** 687 | * Determines if a character is a hex digit [0-9A-Fa-f]. 688 | * 689 | * @return True if the character passed in is a hex digit 690 | */ 691 | private final function isHexDigit( ch:String ):Boolean 692 | { 693 | return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) ); 694 | } 695 | 696 | /** 697 | * Raises a parsing error with a specified message, tacking 698 | * on the error location and the original string. 699 | * 700 | * @param message The message indicating why the error occurred 701 | */ 702 | public final function parseError( message:String ):void 703 | { 704 | throw new JSONParseError( message, loc, jsonString ); 705 | } 706 | } 707 | 708 | } -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | /* CSS file */ 2 | @namespace s "library://ns.adobe.com/flex/spark"; 3 | @namespace mx "library://ns.adobe.com/flex/mx"; 4 | mx|TabNavigator{ 5 | paddingTop:0; 6 | border:0; 7 | } 8 | s|Button{ 9 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 10 | color: #000; 11 | font-size: 12px; 12 | font-weight: normal; 13 | } 14 | s|CheckBox{ 15 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 16 | color: #000; 17 | font-size: 12px; 18 | font-weight: normal; 19 | } 20 | s|TextInput{ 21 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 22 | color: #000; 23 | font-size: 12px; 24 | font-weight: normal; 25 | } 26 | s|Label{ 27 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 28 | color: #000; 29 | font-size: 12px; 30 | font-weight: normal; 31 | } 32 | s|DataGrid{ 33 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 34 | font-size:11px; 35 | } 36 | s|DataGrid s|Label{ 37 | font-family: "Microsoft Yahei","Heiti","黑体","宋体",Helvetica,Arial; 38 | color: #000; 39 | font-size: 12px; 40 | font-weight: normal; 41 | } 42 | mx|Canvas.videoWrapper{ 43 | backgroundColor:#000; 44 | } 45 | s|Button.playBtn{ 46 | skinClass: ClassReference("skins.btnPlaySkin"); 47 | } 48 | s|Button.pauseBtn{ 49 | skinClass: ClassReference("skins.btnPauseSkin"); 50 | } 51 | s|Button.stopBtn{ 52 | skinClass: ClassReference("skins.btnStopSkin"); 53 | } 54 | s|Button.fsBtn{ 55 | skinClass: ClassReference("skins.btnFullScreenSkin"); 56 | } 57 | s|Button.ssBtn{ 58 | skinClass: ClassReference("skins.btnSmallScreenSkin"); 59 | } 60 | s|Button.cfgBtn{ 61 | skinClass: ClassReference("skins.btnSidebarOnSkin"); 62 | } 63 | s|Button.cfgSBtn{ 64 | skinClass: ClassReference("skins.btnSidebarOffSkin"); 65 | } 66 | s|Button.volume{ 67 | skinClass: ClassReference("skins.btnVolumeSkin"); 68 | } 69 | s|Button.muted{ 70 | skinClass: ClassReference("skins.btnMuteSkin"); 71 | } 72 | s|Button.cmntOn{ 73 | skinClass: ClassReference("skins.btnCommentOnSkin"); 74 | } 75 | s|Button.cmntOff{ 76 | skinClass: ClassReference("skins.btnCommentOffSkin"); 77 | } 78 | s|SkinnableComponent.Bar{ 79 | skinClass: ClassReference("skins.containerBar"); 80 | } -------------------------------------------------------------------------------- /src/org/kanoha/collections/LinkedList.as: -------------------------------------------------------------------------------- 1 | /************************* 2 | * LinkedList Modified from tamaki.collection.LinkedList 3 | * ***********************/ 4 | 5 | package org.kanoha.collections 6 | { 7 | public class LinkedList 8 | { 9 | protected var head:Node; 10 | protected var tail:Node; 11 | protected var pntr:Node; 12 | protected var length:uint = 0; 13 | public function LinkedList() 14 | { 15 | head = new Node(); 16 | tail = new Node(); 17 | pntr = head; 18 | head.next = tail; 19 | tail.prev = head; 20 | } 21 | public function push(item:*):void{ 22 | var newNode:Node = new Node(item); 23 | tail.prev.next = newNode; 24 | newNode.prev = tail.prev; 25 | newNode.next = tail; 26 | tail.prev = newNode; 27 | length++; 28 | } 29 | public function shift():*{ 30 | pntr = head; 31 | if(head.next == tail){ 32 | return null;//nothing left! 33 | } 34 | var tn:Node = head.next; 35 | head.next = head.next.next; 36 | head.next.prev = head; 37 | length--; 38 | return tn.data; 39 | } 40 | public function pop():*{ 41 | pntr = head; 42 | if(tail.prev == head){ 43 | return null;//nothing to pop 44 | } 45 | var tn:Node = tail.prev; 46 | tail.prev = tail.prev.prev; 47 | tail.prev.next = tail; 48 | length--; 49 | return tn.data; 50 | } 51 | public function current():*{ 52 | if(!pntr){ 53 | return null; 54 | } 55 | return pntr.data; 56 | } 57 | public function forward():*{ 58 | if(pntr.next == tail){ 59 | return null;//already at the end 60 | } 61 | var tn:Node = pntr.next; 62 | pntr = pntr.next; 63 | return tn.data; 64 | } 65 | public function back():*{ 66 | if(pntr.prev == head){ 67 | return null;//already at the beginning 68 | } 69 | var tn:Node = pntr.prev; 70 | pntr = pntr.prev; 71 | return tn.data; 72 | } 73 | public function hasNext():Boolean{ 74 | if(pntr.next == tail){ 75 | return false; 76 | } 77 | return true; 78 | } 79 | public function hasPrev():Boolean{ 80 | if(pntr.prev == head){ 81 | return false; 82 | } 83 | return true; 84 | } 85 | public function reset():void{ 86 | this.pntr = this.head; 87 | } 88 | public function peek():*{ 89 | if(pntr.next==tail) 90 | return null; 91 | return pntr.next.data; 92 | } 93 | public function peekback():*{ 94 | if(pntr.prev==head) 95 | return null 96 | return pntr.prev.data; 97 | } 98 | public function goLast():void{ 99 | this.pntr = this.tail; 100 | } 101 | public function visit(f:Function, target:*=null):void{ 102 | this.reset(); 103 | while(this.hasNext()){ 104 | var item:* = this.forward(); 105 | f.call(target,item); 106 | } 107 | } 108 | public function filter(f:Function, target:*=null):void{ 109 | this.reset(); 110 | while(this.hasNext()){ 111 | var item:* = this.forward(); 112 | if(!f.call(target,item)){ 113 | this.removeCurrent(); 114 | } 115 | } 116 | } 117 | public function remove(item:*):Boolean{ 118 | this.reset(); 119 | while(this.hasNext()){ 120 | var ti:* = this.forward(); 121 | if(ti==item){ 122 | this.removeCurrent(); 123 | return true; 124 | } 125 | } 126 | return false; 127 | } 128 | public function removeCurrent():void{ 129 | if(pntr!= head && pntr != tail){ 130 | pntr.prev.next = pntr.next; 131 | pntr.next.prev = pntr.prev; 132 | pntr = pntr.prev; 133 | length--; 134 | } 135 | } 136 | public function insertAfterCurrent(item:*):void{ 137 | if(pntr !=tail){ 138 | var tn:Node = new Node(item); 139 | tn.prev = pntr; 140 | tn.next = pntr.next; 141 | pntr.next.prev = tn; 142 | pntr.next = tn; 143 | } 144 | } 145 | public function get size():uint{ 146 | return this.length; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/org/kanoha/collections/Node.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.collections 2 | { 3 | public class Node 4 | { 5 | public var next:Node; 6 | public var prev:Node; 7 | public var data:*; 8 | public function Node(data:* = null,next:Node = null, prev:Node = null) 9 | { 10 | this.prev = prev; 11 | this.next = next; 12 | this.data = data; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/org/kanoha/collections/SortedLinkedList.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.collections 2 | { 3 | public class SortedLinkedList extends LinkedList 4 | { 5 | private var compF:Function; 6 | public function SortedLinkedList(cf:Function) 7 | { 8 | this.compF = cf; 9 | } 10 | override public function push(item:*):void{ 11 | var tn:Node = new Node(item); 12 | var next:Node = this.findPos(item); 13 | if(next == head){ 14 | super.push(item); 15 | }else{ 16 | tn.next = next; 17 | tn.prev = next.prev; 18 | next.prev.next = tn; 19 | next.prev = tn; 20 | length++; 21 | } 22 | } 23 | public function pushReverse(item:*):void{ 24 | var tn:Node = new Node(item); 25 | var pre:Node = this.findPosReverse(item); 26 | if(pre == tail) 27 | { 28 | super.push(item); 29 | } 30 | else 31 | { 32 | tn.prev = pre; 33 | tn.next = pre.next; 34 | pre.next.prev = tn; 35 | pre.next = tn; 36 | length++; 37 | } 38 | } 39 | private function findPos(item:*):Node{ 40 | this.reset(); 41 | while(this.hasNext()){ 42 | var ti:* = this.forward(); 43 | if(this.compF.call(null,item,ti) < 0){ 44 | break; 45 | } 46 | } 47 | if(pntr!=head && pntr.next == tail){ 48 | //LAST ELEMENT 49 | if(this.compF.call(null,item,this.current()) >=0){ 50 | return tail; 51 | } 52 | } 53 | return pntr; 54 | } 55 | private function findPosReverse(item:*):Node{ 56 | this.goLast(); 57 | while(this.hasPrev()){ 58 | var ti:* = this.back(); 59 | if(this.compF.call(null,item,ti)>=0){ 60 | break; 61 | } 62 | } 63 | if(pntr!=tail && pntr.prev==head){ 64 | if(this.compF.call(null,item,ti)<0){ 65 | return head; 66 | } 67 | } 68 | return pntr; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/BottomCommentSpaceAllocator.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | public class BottomCommentSpaceAllocator extends CommentSpaceAllocator 4 | { 5 | public function BottomCommentSpaceAllocator() 6 | { 7 | } 8 | override protected function transformY(y:int,cmt:Comment):int 9 | { 10 | return this.Height - y - cmt.height; 11 | } 12 | override protected function vCheck(y:int, cmt:Comment, index:int):Boolean 13 | { 14 | var bottom:int = y + cmt.height; 15 | for each(var c:Comment in this.pools[index]) 16 | { 17 | var _y:int = transformY(c.y,c); 18 | if(_y > bottom || c.bottom < y) 19 | { 20 | continue; 21 | } 22 | else 23 | { 24 | return false; 25 | } 26 | } 27 | return true; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/Comment.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | /** 4 | * Comment Object 5 | * Author: CQZ 6 | * This is a combo of MukioPlayerPlus and PAD 7 | * Version 2 - This now uses a different approach as we should support 8 | * Image Based comments too. 9 | * This object represents the specialties of both systems 10 | **/ 11 | import flash.text.TextFormat; 12 | 13 | import spark.components.RichText; 14 | import spark.effects.Animate; 15 | import spark.primitives.Rect; 16 | 17 | public class Comment extends RichText 18 | { 19 | protected var _data:Object; 20 | protected var borderBox:Rect; 21 | protected var _index:int;//tracking 22 | protected var _bottom:int;//tracking 23 | protected var _effect:Animate; 24 | protected var _duration:Number=0; 25 | protected var _containerWidth:int=0; 26 | protected var config:CommentConfig = new CommentConfig();//default configurations?! 27 | public function Comment(d:Object){ 28 | super(); 29 | _data = {}; 30 | for (var key:String in d){ 31 | _data[key] = d[key]; 32 | } 33 | //init(); 34 | } 35 | public function setY(py:int,idx:int,trans:Function):void{ 36 | this.y = trans(py,this); 37 | this._index = idx; 38 | this._bottom = py + this.height; 39 | } 40 | public function get index():int{ 41 | return this._index; 42 | } 43 | override public function get bottom():Object{ 44 | return this._bottom; 45 | } 46 | override public function get right():Object{ 47 | return this.x + this.width; 48 | } 49 | public function set duration(dur:Number):void{ 50 | this._duration = dur; 51 | } 52 | public function get duration():Number{ 53 | return this._duration; 54 | } 55 | public function get stime():Number{ 56 | return this._data['stime']; 57 | } 58 | public function validateEX():void{ 59 | this.validateNow(); 60 | this.height = this.measuredHeight; 61 | this.width = this.measuredWidth; 62 | } 63 | public function init():void{ 64 | this.visible = true; 65 | if(config.bold){ 66 | this.setStyle("fontWeight","bold"); 67 | } 68 | //this.textField.defaultTextFormat = new TextFormat(config.font,config.relsize * _data.size, _data.color,config.bold); 69 | this.setStyle("fontSize",config.relsize * _data.size); 70 | this.setStyle("fontFamily",config.font); 71 | this.setStyle("color",_data.color); 72 | this.toolTip=""; 73 | if(_data.mode<6){ 74 | this.alpha = config.alpha; 75 | } 76 | this.text = _data.text; 77 | if(_data.border){ 78 | trace("Border"); 79 | } 80 | /* 81 | if(_data.borderColor!=null){ 82 | this.textField.borderColor = _data.borderColor; 83 | }else{ 84 | this.textField.borderColor = 0x66FFFF;//hmmm 85 | }*/ 86 | if(this._data.mode>=7){ 87 | this.x = this._data.x; 88 | this.y = this._data.y; 89 | this.rotationZ = this._data.rZ; 90 | this.rotationY = this._data.rY; 91 | //this.rotation = _data.rZ; 92 | //this.rotationZ = _data.rZ; 93 | } 94 | this.filters = config.filter; 95 | if(int(_data.color /65536)<10 && int((_data.color%65536)/256)<10 && (_data.color%256)<10 && (_data.mode == 4 || _data.mode==5)){ 96 | this.filters = config.whiteFilter; 97 | } 98 | this.validateEX(); 99 | } 100 | public function set borderColor(c:uint):void{ 101 | //this.textField.borderColor = c; 102 | } 103 | public function set effect(effect:Animate):void{ 104 | this._effect = effect; 105 | } 106 | public function get effect():Animate{ 107 | return this._effect; 108 | } 109 | public function get dataObject():Object{ 110 | return this._data; 111 | } 112 | public function set defaults(def:CommentConfig):void{ 113 | this.config = def; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/CommentConfig.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | public class CommentConfig 4 | { 5 | import flash.filters.GlowFilter; 6 | private var _$settings:Object = { 7 | bold:true, 8 | alpha:1, 9 | filter:[new GlowFilter(0, 0.7, 3,3)], 10 | whiteFilter:[new GlowFilter(0xffffff, 0.7, 3,3)], 11 | relsize:1, 12 | font:"宋体" 13 | }; 14 | public function CommentConfig(init:Object = null) 15 | { 16 | if(init!=null) 17 | this._$settings = init; 18 | } 19 | public static function getDefaults():Object{ 20 | var obj:Object = { 21 | bold:true, 22 | alpha:1, 23 | filter:[new GlowFilter(0, 0.7, 3,3)], 24 | whiteFilter:[new GlowFilter(0xffffff, 0.7, 3,3)], 25 | relsize:1, 26 | font:"宋体" 27 | }; 28 | return obj; 29 | } 30 | public function get bold():Boolean{ 31 | return this._$settings.bold; 32 | } 33 | public function get alpha():Number{ 34 | return this._$settings.alpha; 35 | } 36 | public function get filter():Array{ 37 | return this._$settings.filter; 38 | } 39 | public function get whiteFilter():Array{ 40 | return this._$settings.whiteFilter; 41 | } 42 | public function get relsize():Number{ 43 | return this._$settings.relsize; 44 | } 45 | public function get font():String{ 46 | return this._$settings.font; 47 | } 48 | public function setStyle(styleKey:String,styleValue:*):void{ 49 | this._$settings[styleKey] = styleValue; 50 | } 51 | public function setGlow(glowMode:String):void{ 52 | if(glowMode=='none'){ 53 | this._$settings['filter']=[]; 54 | this._$settings['whiteFilter']=[]; 55 | }else if(glowMode=='border'){ 56 | this._$settings['filter']=[new GlowFilter(0, 0.7, 3,3)]; 57 | this._$settings['whiteFilter']=[new GlowFilter(0xffffff, 0.7, 3,3)]; 58 | }else if(glowMode=="highlight"){ 59 | this._$settings['filter']=[new GlowFilter(0x34251e, 0.7, 6,6)]; 60 | this._$settings['whiteFilter']=[new GlowFilter(0x34251e, 0.7, 6,6)]; 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/CommentFilter.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | public class CommentFilter 4 | { 5 | protected var filters:Array = new Array(); 6 | protected var filterModes:Array = [true,true,true,true,true,true,true,true,true,true]; 7 | public function CommentFilter() 8 | { 9 | } 10 | public function clear():void{ 11 | this.filters = new Array();//deleted all filters 12 | } 13 | public function addFilter(filter:String):Object{ 14 | //Parse this 15 | var entities:Array = filter.split(" ",3); 16 | if(entities.length<3) 17 | return {id:-1};//Not enough parameters 18 | var entMode:Array = String(entities[0]).split(".",2); 19 | var id:int=0; 20 | switch(entMode[0]){ 21 | case '$':id=1;break; 22 | case 'T':id=5;break; 23 | case 'B':id=4;break; 24 | case 'P':id=7;break; 25 | case 'R':id=6;break; 26 | case '*':id=0;break; 27 | default:return {id:-2};//Undefined entity mode 28 | } 29 | if(filters[id]==null){ 30 | filters[id]=new Array(); 31 | } 32 | var objFilter:Object = {property:entMode[1],operator:entities[1],p:entities[2]}; 33 | if(objFilter.operator != "region" && objFilter.operator != "subset") 34 | if(objFilter.property == "size" ||objFilter.property == "mode"|| objFilter.property == "stime" || objFilter.property == "width" || objFilter.property == "height" || objFilter.property == "color" && objFilter.p.charAt(0)!="#"){ 35 | objFilter.p=int(objFilter.p); 36 | } 37 | filters[id].push(objFilter); 38 | return {id:(filters[id].length - 1),mode:id}; 39 | } 40 | public function removeFilterAt(id:int,mode:int=0):Boolean{ 41 | try{ 42 | filters[mode].splice(id,1); 43 | }catch(e:Error){ 44 | //do nothing 45 | return false; 46 | } 47 | return true; 48 | } 49 | public function removeFilter(filter:String):Number{ 50 | //Parse this 51 | var entities:Array = filter.split(" ",3); 52 | if(entities.length<3) 53 | return -1;//Not enough parameters 54 | var entMode:Array = String(entities[0]).split(".",2); 55 | var id:int=0; 56 | switch(entMode[0]){ 57 | case '$':id=1;break; 58 | case 'T':id=5;break; 59 | case 'B':id=4;break; 60 | case 'P':id=7;break; 61 | case 'R':id=6;break; 62 | case '*':id=0;break; 63 | default:return -2;//Undefined entity mode 64 | } 65 | if(filters[id]==null){ 66 | return 0; 67 | } 68 | var objFilter:Object = {property:entMode[1],operator:entities[1],p:entities[2]}; 69 | if(objFilter.operator != "region" && objFilter.operator != "subset") 70 | if(objFilter.property == "size" ||objFilter.property == "mode"|| objFilter.property == "stime" || objFilter.property == "width" || objFilter.property == "height" || objFilter.property == "color" && objFilter.p.charAt(0)!="#"){ 71 | objFilter.p=int(objFilter.p); 72 | } 73 | for(var m:int=0;mNumber(fdata.p)){ 166 | return false; 167 | } 168 | return true; 169 | } 170 | case 'subset': 171 | case 'region':{ 172 | if(fdata.property=="text" || fdata.property=="hash"){ 173 | return true; 174 | } 175 | if(fdata.property=="color" && String(fdata.p).charAt(0)=="#"){ 176 | //decode this color 177 | fdata.p = uint("0x" + String(fdata.p).substr(1,String(fdata.p).length));//numberd color 178 | } 179 | var limits:Array = String(fdata.p).split(","); 180 | if(cdata[fdata.property]!=null && Number(cdata[fdata.property])>Number(limits[0]) && Number(cdata[fdata.property])0){ 94 | this._$container.removeChildAt(0); 95 | } 96 | } 97 | public function pause():void{ 98 | this.isPlaying=false; 99 | for(var i:int = 0;i<_$container.numChildren;i++){ 100 | var c:Comment = Comment(_$container.getChildAt(i)); 101 | if(c!=null && c.effect!=null){ 102 | c.effect.pause(); 103 | } 104 | } 105 | } 106 | public function resume():void{ 107 | for(var i:int = 0;i<_$container.numChildren;i++){ 108 | var c:Comment = Comment(_$container.getChildAt(i)); 109 | if(c!=null && c.effect!=null){ 110 | c.effect.resume(); 111 | } 112 | } 113 | this.isPlaying=true; 114 | } 115 | public function insert(commentData:Object):void{ 116 | var obj:Object = {on:false}; 117 | for (var key:String in commentData){ 118 | obj[key] = commentData[key]; 119 | } 120 | if(obj.border){ 121 | this.start(obj);//only run if it is playing 122 | } 123 | if(obj.preview){ 124 | return;//Don't really add for future plays 125 | } 126 | var p:int = binsert(this.timeline,obj,function(a:*,b:*):int{ 127 | if(a.time < b.time) 128 | return -1; 129 | else 130 | { 131 | if(a.time > b.time){ 132 | return 1; 133 | }else{ 134 | if(a.date < b.date){ 135 | return -1; 136 | }else if(a.date > b.date){ 137 | return 1; 138 | }else{ 139 | return 0; 140 | } 141 | 142 | } 143 | } 144 | }); 145 | if(p <= this.position){ 146 | this.position++;//inserted before so bump position up 147 | } 148 | } 149 | protected function start(data:Object):void{ 150 | if(data.mode >= 6 && !this._$handleAdv){ 151 | return;//Skip this 152 | } 153 | if(this._$cpuF && prepare_stack.length > 20){ 154 | return;//skip it 155 | } 156 | data['on'] = false; 157 | //data['border']=true; 158 | var cmt:Comment = new Comment(data); 159 | cmt.defaults = this.cmSettings; 160 | //new comment 161 | var self:CommentManager = CommentManager(this); 162 | _$container.addChild(cmt);//add to container 163 | cmt.init(); 164 | this.allocSpace(cmt); 165 | if(!this._$handleAdv){ 166 | if(data.mode<4){ 167 | var eff:Move = new Move(cmt); 168 | eff.easer = new Linear(0,0); 169 | eff.duration = 4000; 170 | eff.xFrom = _$container.width; 171 | eff.xTo = 0 - cmt.width; 172 | cmt.effect = eff; 173 | cmt.effect.play(); 174 | }else if(data.mode==4 || data.mode==5){ 175 | var eff2:Fade = new Fade(cmt); 176 | eff2.alphaTo = 1; 177 | eff2.alphaFrom = 1; 178 | eff2.duration = 4000; 179 | cmt.effect=eff2; 180 | cmt.effect.play(); 181 | }else{ 182 | trace('unknown mode!'); 183 | } 184 | }else{ 185 | if(data.mode==7 && this._$handleAdv){ 186 | if(data.movable == false){ 187 | var advFade:Fade = new Fade(cmt); 188 | advFade.alphaTo = data.outAlpha; 189 | advFade.alphaFrom = data.inAlpha; 190 | advFade.duration = data.duration * 1000; 191 | cmt.effect = advFade; 192 | cmt.effect.play(); 193 | }else{ 194 | trace("New Movable type Effect: " + data.toX + " " + data.toY + " DUR:" + data.moveDuration + " Delay:" + data.moveDelay); 195 | var advEvt:TimelineEffect = new TimelineEffect(); 196 | var advFade2:Fade = new Fade(cmt); 197 | advFade2.alphaTo = data.outAlpha; 198 | advFade2.alphaFrom = data.inAlpha; 199 | advFade2.duration = data.duration * 1000; 200 | var advMove:Move = new Move(cmt); 201 | advMove.xTo = data.toX; 202 | advMove.yTo = data.toY; 203 | advMove.startDelay = data.moveDelay; 204 | advMove.duration = data.moveDuration; 205 | advEvt.addEffect(advFade2); 206 | advEvt.addEffect(advMove); 207 | cmt.effect = advEvt; 208 | cmt.effect.play(); 209 | } 210 | }else if(data.mode==6 && this._$handleAdv){ 211 | var effc:Move = new Move(cmt); 212 | effc.easer = new Linear(0,0); 213 | effc.duration = 4000; 214 | effc.xFrom = 0 - cmt.width; 215 | effc.xTo = _$container.width; 216 | cmt.effect = effc; 217 | cmt.effect.play(); 218 | } 219 | } 220 | if(cmt.effect!=null){ 221 | cmt.effect.addEventListener(EffectEvent.EFFECT_END,function ():void{ 222 | self.complete(cmt); 223 | self.deallocSpace(cmt); 224 | self._$container.removeChild(cmt); 225 | cmt = null; 226 | }); 227 | }else{ 228 | this.complete(cmt); 229 | this.deallocSpace(cmt); 230 | this._$container.removeChild(cmt); 231 | cmt = null;//Free up space for comment 232 | return;//unhandled comments need not be pushed into run stack! 233 | //unhandled comment!; 234 | } 235 | this.prepare_stack.push(cmt); 236 | } 237 | protected function allocSpace(cm:Comment):void{ 238 | if(cm.dataObject.mode<4){ 239 | this.salloc_scroll.add(cm); 240 | }else if(cm.dataObject.mode==5){ 241 | this.salloc_top.add(cm); 242 | }else if(cm.dataObject.mode==4){ 243 | this.salloc_bottom.add(cm); 244 | } 245 | } 246 | protected function deallocSpace(cm:Comment):void{ 247 | if(cm.dataObject.mode<4){ 248 | this.salloc_scroll.remove(cm); 249 | }else if(cm.dataObject.mode==5){ 250 | this.salloc_top.remove(cm); 251 | }else if(cm.dataObject.mode==4){ 252 | this.salloc_bottom.remove(cm); 253 | } 254 | } 255 | protected function getComment(data:Object):Comment{ 256 | return new Comment(data); 257 | } 258 | protected function complete(cmt:Comment):void{ 259 | var i:int = this.prepare_stack.indexOf(cmt); 260 | this.prepare_stack.splice(i,1);//remove from current running 261 | } 262 | public function time(t:Number):void{ 263 | if(!this.isPlaying){ 264 | return;//not playing 265 | } 266 | t = t - 1; 267 | if(this.position >= this.timeline.length || Math.abs(this.lastPos - t) >= 2000){ 268 | this.seek(t); 269 | this.clear(); 270 | this.lastPos = t; 271 | if(this.timeline.length <= this.position){ 272 | return; 273 | } 274 | }else{ 275 | this.lastPos = t; 276 | } 277 | for(;this.position < this.timeline.length;this.position++){ 278 | if(this.getData(this.position)['stime'] <= t){ 279 | if(this.validate(this.getData(this.position))){ 280 | this.start(this.getData(this.position)); 281 | } 282 | }else{ 283 | break; 284 | } 285 | } 286 | } 287 | protected function getData(id:int):Object{ 288 | //safety wrapper 289 | if(id>=0 && id < this.timeline.length){ 290 | return this.timeline[id]; 291 | } 292 | return null;//npe 293 | } 294 | protected function seek(time:Number):void{ 295 | this.position = bsearch(this._$list,time,function(pos:*,data:*):int{ 296 | if(pos < data.stime){ 297 | return -1; 298 | }else if(pos > data.stime){ 299 | return 1; 300 | }else{ 301 | return 0; 302 | } 303 | }); 304 | } 305 | protected function validate(data:Object):Boolean{ 306 | if(data['on']){ 307 | return false;//dont send again 308 | } 309 | return _$filter.validate(data);//no filter caps yet! 310 | return true; 311 | } 312 | /** 313 | * BSearch 314 | **/ 315 | public static function bsearch(arr:Array,a:*,f:Function):int{ 316 | if(arr.length==0) 317 | return 0;//Nothing to search 318 | if(f(a,arr[0])<0) 319 | return 0;//Smaller than smallest 320 | if(f(a, arr[arr.length-1])>=0) 321 | return arr.length;//Bigger than biggest 322 | var low:int = 0; 323 | var i:int; 324 | var count:int = 0; 325 | var high:int = arr.length - 1; 326 | while(low<=high){ 327 | i = Math.floor((high + low + 1)/2);//find middle 328 | count++; 329 | if(f(a,arr[i-1])>=0 && f(a,arr[i])<0){ 330 | return i; 331 | }else if(f(a, arr[i-1]) < 0){ 332 | high = i-1; 333 | }else if(f(a, arr[i]) >=0){ 334 | low = i; 335 | }else{ 336 | throw new Error("BSearch Unexpected Error"); 337 | } 338 | if(count > 1500){ 339 | throw new Error("BSearch Execute Limit Exceed"); 340 | break; 341 | } 342 | } 343 | return -1;//Wha? 344 | } 345 | public static function binsert(arr:Array,elem:*,f:Function):Number{ 346 | var i:int = bsearch(arr,elem,f); 347 | arr.splice(i,0,elem); 348 | return i; 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/CommentSpaceAllocator.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | /** 4 | * A Very Good Comment Space Allocator 5 | * @aristotle9 package mukioplayerplus 6 | * ported and edited for ABPlayer 7 | **/ 8 | public class CommentSpaceAllocator 9 | { 10 | protected var pools:Array = new Array(); 11 | protected var Width:int; 12 | protected var Height:int; 13 | public function CommentSpaceAllocator() 14 | { 15 | } 16 | public function setBounds(w:int,h:int):void{ 17 | this.Width = w; 18 | this.Height = h; 19 | } 20 | public function add(cmt:Comment):void{ 21 | cmt.x = (this.Width - cmt.width)/2; 22 | if(cmt.height >= this.Height){ 23 | cmt.setY(0,-1,transformY); 24 | }else{ 25 | //This needs calculation 26 | this.setY(cmt); 27 | } 28 | } 29 | public function setY(cmt:Comment,index:int = 0):void{ 30 | var y:int =0; 31 | if(this.pools.length <= index){ 32 | this.pools.push(new Array()); 33 | } 34 | var pool:Array = this.pools[index]; 35 | if(pool.length == 0){ 36 | cmt.setY(0,index,transformY); 37 | pool.push(cmt); 38 | return; 39 | } 40 | if(this.vCheck(0,cmt,index)){ 41 | cmt.setY(0,index,transformY); 42 | CommentManager.binsert(pool,cmt,bottom_cmp); 43 | return; 44 | } 45 | for each(var c:Comment in pool){ 46 | y = c.bottom + 1; 47 | if(y + cmt.height > this.Height){ 48 | break; 49 | } 50 | if(this.vCheck(y,cmt,index)){ 51 | cmt.setY(y,index,transformY); 52 | CommentManager.binsert(pool,cmt,bottom_cmp); 53 | return; 54 | } 55 | } 56 | this.setY(cmt,index +1); 57 | } 58 | protected function transformY(y:int,c:Comment):int{ 59 | return y; 60 | } 61 | protected function bottom_cmp(a:Comment,b:Comment):int{ 62 | if(a.bottom bottom || c.bottom < y){ 75 | continue; 76 | }else{ 77 | return false; 78 | } 79 | } 80 | return true; 81 | } 82 | public function remove(cmt:Comment):void{ 83 | if(cmt.index != -1){ 84 | var pool:Array = this.pools[cmt.index]; 85 | var n:int = pool.indexOf(cmt); 86 | pool.splice(n,1); 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/ScrollCommentSpaceAllocator.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment 2 | { 3 | public class ScrollCommentSpaceAllocator extends CommentSpaceAllocator 4 | { 5 | private var dur:int = 4; 6 | public function ScrollCommentSpaceAllocator() 7 | { 8 | } 9 | override public function add(cmt:Comment):void 10 | { 11 | cmt.x = this.Width; 12 | cmt.duration = (this.Width + cmt.width) / this.getSpeed(cmt); 13 | if(cmt.height >= this.Height) 14 | { 15 | cmt.setY(0,-1,transformY); 16 | } 17 | else 18 | { 19 | this.setY(cmt); 20 | } 21 | } 22 | override protected function vCheck(y:int, cmt:Comment, index:int):Boolean 23 | { 24 | var bottom:int = y + cmt.height; 25 | var right:int = cmt.x + cmt.width; 26 | for each(var c:Comment in this.pools[index]) 27 | { 28 | if(c.y > bottom || c.bottom < y) 29 | { 30 | continue; 31 | } 32 | else if(c.right < cmt.x || c.x > right) 33 | { 34 | if(this.getEnd(c) <= this.getMiddle(cmt)) 35 | { 36 | continue; 37 | } 38 | else 39 | { 40 | return false; 41 | } 42 | } 43 | else 44 | { 45 | return false; 46 | } 47 | } 48 | return true; 49 | } 50 | private function getSpeed(cmt:Comment):Number 51 | { 52 | return 1 * 0.5 * (this.Width + cmt.width) / this.dur; 53 | } 54 | private function getEnd(cmt:Comment):Number 55 | { 56 | return cmt.stime + (this.Width + cmt.width) / this.getSpeed(cmt); 57 | } 58 | private function getMiddle(cmt:Comment):Number 59 | { 60 | return cmt.stime + this.Width / this.getSpeed(cmt); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/org/kanoha/comment/effects/TimelineEffect.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.comment.effects 2 | { 3 | import mx.effects.Effect; 4 | import mx.effects.IEffectInstance; 5 | import mx.events.EffectEvent; 6 | 7 | import spark.effects.Animate; 8 | 9 | /*** 10 | * Timelined Effect 11 | * @Package ABPlayer 12 | * A Special Effect for offsetting multiple effects together 13 | * **/ 14 | public class TimelineEffect extends Animate 15 | { 16 | protected var _$eff:Array = new Array(); 17 | protected var _$ran:int = 0; 18 | public function TimelineEffect() 19 | { 20 | } 21 | public function addEffect(eff:Animate):void{ 22 | this._$eff.push(eff); 23 | } 24 | public function removeEffect(eff:Animate):void{ 25 | var rid:int = this._$eff.indexOf(eff); 26 | this._$eff.splice(rid,1); 27 | } 28 | private function onEffectEnd(e:EffectEvent):void{ 29 | this._$ran++; 30 | trace(_$ran); 31 | if(_$ran==_$eff.length){ 32 | this.dispatchEvent(new EffectEvent(EffectEvent.EFFECT_END)); 33 | trace("Multiple Effect Finished"); 34 | } 35 | } 36 | override public function end(inst:IEffectInstance = null):void{ 37 | //End all effects 38 | for(var i:int = 0;i<_$eff.length;i++){ 39 | if(_$eff[i]!=null && Effect(_$eff[i]).isPlaying) 40 | _$eff[i].end(); 41 | } 42 | //The end dispatch event will be automatically dispatched 43 | } 44 | override public function resume():void{ 45 | for(var i:int = 0;i<_$eff.length;i++){ 46 | if(_$eff[i]!=null && Effect(_$eff[i]).isPlaying) 47 | _$eff[i].resume(); 48 | } 49 | } 50 | override public function pause():void{ 51 | for(var i:int = 0;i<_$eff.length;i++){ 52 | if(_$eff[i]!=null && Effect(_$eff[i]).isPlaying) 53 | _$eff[i].pause(); 54 | } 55 | } 56 | override public function play(targets:Array=null,reverse:Boolean=false):Array{ 57 | //Get count 58 | if(this._$eff.length ==0){ 59 | this.dispatchEvent(new EffectEvent(EffectEvent.EFFECT_END));//Nothing to do 60 | return null; 61 | } 62 | for(var i:int=0;iq && int(video.attribute("brt"))<=4){ 48 | q = int(video.attribute("brt"));//find highest quality 49 | v = video.toString(); 50 | } 51 | } 52 | var tdInfo:VideoInfo = new VideoInfo(v,uint(xmlObj.attribute("time")),0); 53 | return {video:[tdInfo],duration:uint(xmlObj.attribute("time"))}; 54 | }else{ 55 | return {}; 56 | } 57 | } 58 | private function onError():void{ 59 | trace('An error occurred'); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/org/kanoha/net/XMLLoader.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.net 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.net.URLLoader; 6 | import flash.net.URLRequest; 7 | import flash.events.IOErrorEvent; 8 | import org.kanoha.events.LoaderEvent; 9 | public class XMLLoader extends EventDispatcher 10 | { 11 | private var loader:URLLoader = new URLLoader(); 12 | public function XMLLoader() 13 | { 14 | this.hookListeners(); 15 | } 16 | public function load(url:String):void{ 17 | var req:URLRequest = new URLRequest(url); 18 | loader.load(req); 19 | } 20 | private function hookListeners():void{ 21 | loader.addEventListener(Event.COMPLETE,onComplete); 22 | loader.addEventListener(IOErrorEvent.IO_ERROR,onError); 23 | } 24 | private function onComplete(e:Event):void{ 25 | try{ 26 | var xml:XML = new XML(loader.data); 27 | this.dispatchEvent(new LoaderEvent(LoaderEvent.XML_COMPLETE,xml)); 28 | }catch(e:Error){ 29 | this.dispatchEvent(new LoaderEvent(LoaderEvent.XML_PARSE_ERROR)); 30 | } 31 | } 32 | private function onError():void{ 33 | this.dispatchEvent(new LoaderEvent(LoaderEvent.XML_LOAD_ERROR)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/org/kanoha/util/CommentListParser.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.util 2 | { 3 | public class CommentListParser 4 | { 5 | public function CommentListParser() 6 | { 7 | } 8 | public static function parse(input:XML,mode:int = 0):Array{ 9 | if(mode==0){ 10 | return CommentListParser.AvXML2Comment(input); 11 | } 12 | return new Array(); 13 | } 14 | public static function AvXML2Comment(input:XML):Array{ 15 | var ret:Array=new Array(); 16 | var tmp:XMLList = input.d; 17 | var id:int = 0; 18 | for each(var nd:XML in tmp){ 19 | var p:Array = String(nd.@p).split(','); 20 | var text:String = String(nd); 21 | var obj:Object = {}; 22 | obj.stime = uint(parseFloat(p[0])*1000); 23 | obj.size = uint(p[2]); 24 | obj.color = uint(p[3]); 25 | obj.mode = uint(p[1]); 26 | obj.date = new Date(p[4] * 1000); 27 | obj.timestamp = int(p[4]); 28 | obj.pool = uint(p[5]); 29 | obj.hash = p[6]; 30 | obj.border = false; 31 | obj.id = id ++; 32 | if(obj.mode<7){ 33 | obj.text = text.replace(/(\/n|\\n|\n|\r\n)/g, "\r"); 34 | }else{ 35 | if(obj.mode==7){ 36 | try{ 37 | var json:Object = JSON.parse(text); 38 | obj.x = Number(json[0]); 39 | obj.y = Number(json[1]); 40 | obj.text = String(json[4]).replace(/(\/n|\\n|\n|\r\n)/g, "\r"); 41 | obj.rZ = 0; 42 | obj.rY = 0; 43 | if(json.length >= 7){ 44 | obj.rZ = Number(json[5]); 45 | obj.rY = Number(json[6]); 46 | } 47 | obj.movable = false; 48 | //insert advanced mode here 49 | if(json.length >=11){ 50 | obj.movable = true; 51 | obj.toX = Number(json[7]); 52 | obj.toY = Number(json[8]); 53 | obj.moveDuration = 500; 54 | obj.moveDelay = 0; 55 | if(json[9]!='') 56 | obj.moveDuration = Number(json[9]); 57 | if(json[10]!='') 58 | obj.moveDelay = Number(json[10]); 59 | } 60 | obj.duration = 2.5; 61 | if(json[3] < 12 && json[3] != 1){ 62 | obj.duration = Number(json[3]); 63 | } 64 | obj.inAlpha = 1; 65 | obj.outAlpha = 1; 66 | var a:Array = String(json[2]).split('-'); 67 | if(a.length >=2){ 68 | obj.inAlpha = Number(a[0]); 69 | obj.outAlpha = Number(a[1]); 70 | } 71 | }catch(e:Error){ 72 | trace('JSON Failed!?'); 73 | trace(text); 74 | } 75 | }else{ 76 | obj.text = 'Advanced not supported!'; 77 | } 78 | } 79 | ret.push(obj); 80 | } 81 | return ret; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/org/kanoha/util/Filters.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.util 2 | { 3 | import flash.filters.GlowFilter; 4 | public class Filters 5 | { 6 | private static var fs:Array = [new GlowFilter(0,1,6,6,5)]; 7 | private static var fs2:Array = [new GlowFilter(0, 0.7, 3,3)]; 8 | public function Filters() 9 | { 10 | 11 | } 12 | public static function getGlow():Array{ 13 | return fs; 14 | } 15 | public static function getAdvancedGlow():Array{ 16 | return fs2; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/org/kanoha/video/NS.as: -------------------------------------------------------------------------------- 1 | package org.kanoha.video 2 | { 3 | import flash.events.*; 4 | import flash.media.*; 5 | import flash.net.*; 6 | import flash.utils.Timer; 7 | 8 | import org.kanoha.events.NSEvent; 9 | public class NS extends EventDispatcher 10 | { 11 | private var _$id:uint; 12 | private var _$stopSign:Boolean; 13 | private var _$bufferTime:uint; 14 | private var _$nc:NetConnection; 15 | private var _$ns:NetStream; 16 | private var _$is_hc:Boolean; 17 | private var _$isStop:Boolean; 18 | private var _$checkPolicyFile:Boolean = false; 19 | private var _$volume:Number = 0.7; 20 | private var _$btimer:Timer = new Timer(100); 21 | private var _$ptimer:Timer = new Timer(10); 22 | private var _$isLoad:Boolean = false; 23 | public function NS(bufferTime:uint = 5) : void 24 | { 25 | _$bufferTime = bufferTime; 26 | init(); 27 | return; 28 | } 29 | private function init() : void 30 | { 31 | this.addEventListener(NSEvent.CHECK_FULL,onReallyFull) 32 | _$btimer.addEventListener(TimerEvent.TIMER,checkBuff) 33 | _$ptimer.addEventListener(TimerEvent.TIMER,onPlaying) 34 | _$nc = new NetConnection(); 35 | _$nc.addEventListener(NetStatusEvent.NET_STATUS, ncStatus); 36 | _$nc.connect(null); 37 | return; 38 | } 39 | private function onPlaying(eve:TimerEvent):void 40 | { 41 | dispatchEvent(new NSEvent(NSEvent.PLAYING,this.time)); 42 | } 43 | private function checkBuff(eve:TimerEvent):void 44 | { 45 | dispatchEvent(new NSEvent(NSEvent.BUFFERING,this.loadPercent)); 46 | if(this.loadPercent == 1) 47 | { 48 | trace("really full!") 49 | dispatchEvent(new NSEvent(NSEvent.CHECK_FULL)); 50 | this.removeEventListener(TimerEvent.TIMER,checkBuff); 51 | this._$btimer.stop(); 52 | this._$isLoad = true 53 | } 54 | } 55 | 56 | private function ncStatus(param1:NetStatusEvent) : void 57 | { 58 | if (param1.info.code == "NetConnection.Connect.Success") 59 | { 60 | createNS(); 61 | } 62 | return; 63 | } 64 | private function createNS():void 65 | { 66 | _$ns = new NetStream(_$nc); 67 | _$ns.bufferTime = _$bufferTime; 68 | _$ns.client = this; 69 | if(this._$checkPolicyFile){ 70 | _$ns.checkPolicyFile = true; 71 | } 72 | setVolume(); 73 | removeEvent(_$ns); 74 | addEvent(_$ns); 75 | return; 76 | } 77 | 78 | public function set id(param1:uint) : void 79 | { 80 | _$id = param1; 81 | return; 82 | } 83 | public function stopV() : void 84 | { 85 | _$ptimer.stop(); 86 | _$btimer.stop(); 87 | _$ns.pause(); 88 | _$ns.seek(0); 89 | return; 90 | } 91 | public function closeV() : void 92 | { _$ptimer.stop(); 93 | _$btimer.stop(); 94 | _$ns.pause(); 95 | _$ns.close(); 96 | return; 97 | } 98 | public function pauseV() : void 99 | { 100 | _$ptimer.stop(); 101 | _$ns.pause(); 102 | return; 103 | } 104 | public function get volume() : Number 105 | { 106 | return _$volume; 107 | } 108 | private function setVolume(param1:Number = 0.7) : void 109 | { 110 | _$volume = param1; 111 | var st:SoundTransform = new SoundTransform(_$volume, 0); 112 | _$ns.soundTransform = st; 113 | return; 114 | } 115 | public function seekV(param1:Number) : void 116 | { 117 | param1 = Math.floor(param1 / 1000); 118 | _$ns.seek(param1); 119 | return; 120 | } 121 | public function playV() : void 122 | { 123 | _$ptimer.start(); 124 | if(!_$isLoad) 125 | _$btimer.start(); 126 | _$isStop = false; 127 | _$ns.resume(); 128 | return; 129 | } 130 | private function asyncHandler(param1:AsyncErrorEvent) : void 131 | { 132 | trace("AsyncErrorEvent::" + param1.type); 133 | return; 134 | } 135 | public function loadV(param1:String) : void 136 | { 137 | var path:String = param1; 138 | _$isStop = false; 139 | _$is_hc = false; 140 | _$stopSign = false; 141 | try 142 | { 143 | _$ns.play(path); 144 | }// end try 145 | catch (e:Error) 146 | { 147 | trace(e); 148 | }// end catch 149 | _$ns.pause(); 150 | this._$btimer.start() 151 | return; 152 | } 153 | private function addEvent(dispather:NetStream) : void 154 | { 155 | dispather.addEventListener(NetStatusEvent.NET_STATUS, statusHandler); 156 | dispather.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncHandler); 157 | return; 158 | } 159 | private function removeEvent(param1:NetStream) : void 160 | { 161 | param1.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); 162 | param1.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncHandler); 163 | return; 164 | } 165 | 166 | private function statusHandler(param1:NetStatusEvent) : void 167 | { 168 | var loadPercent:Number; 169 | var info:String = param1.info.code; 170 | // trace("stream state:" + info); 171 | switch(info) 172 | { 173 | case "NetStream.Play.Start": 174 | { 175 | dispatchEvent(new NSEvent(NSEvent.READY)); 176 | break; 177 | }// end case 178 | case "NetStream.Play.Stop": //break playing 179 | { 180 | if (!_$isStop)//is playing 181 | { 182 | _$isStop = true; 183 | trace("stopStop"); 184 | dispatchEvent(new NSEvent(NSEvent.STOP)); 185 | }// end if 186 | break; 187 | }// end case 188 | case "NetStream.Buffer.Empty": 189 | { 190 | if (_$stopSign)// is buffer flushed 191 | { 192 | if (!_$isStop)//is playing 193 | { 194 | _$isStop = true;//stopped 195 | trace("emptyStop"); 196 | dispatchEvent(new NSEvent(NSEvent.STOP)); 197 | }// end if 198 | } 199 | else //it's not really the end..wait for buffering... 200 | { 201 | loadPercent = Math.round(_$ns.bufferLength / _$ns.bufferTime * 100) / 100; 202 | dispatchEvent(new NSEvent(NSEvent.EMPTY, loadPercent)); 203 | }// end else if 204 | break; 205 | }// end case 206 | case "NetStream.Buffer.Full": 207 | { 208 | if (!_$is_hc) 209 | { 210 | _$is_hc = true;//firsttime start playing? 211 | dispatchEvent(new NSEvent(NSEvent.PLAY)); 212 | } 213 | else 214 | { 215 | dispatchEvent(new NSEvent(NSEvent.FULL)); 216 | }// end else if 217 | break; 218 | }// end case 219 | case "NetStream.Buffer.Flush": 220 | { //buffer will be clear 221 | if (!_$stopSign) //set buffer stop sign 222 | { 223 | _$stopSign = true; 224 | }// end if 225 | dispatchEvent(new NSEvent(NSEvent.FLUSH)); 226 | break; 227 | }// end case 228 | case "NetStream.Play.StreamNotFound": 229 | { 230 | trace("StreamNotFound"); 231 | dispatchEvent(new NSEvent(NSEvent.FILE_EMPTY)); 232 | break; 233 | }// end case 234 | case "NetStream.Seek.Notify": 235 | { 236 | //seek completed set is flush false 237 | _$stopSign = false; 238 | break; 239 | }// end case 240 | case "NetStream.Seek.Failed": 241 | { 242 | break; 243 | }// end case 244 | case "NetStream.Seek.InvalidTime": 245 | { 246 | dispatchEvent(new NSEvent(NSEvent.SEEK_ERROR)); 247 | break; 248 | }// end case 249 | default: 250 | { 251 | break; 252 | }// end default 253 | }// end switch 254 | return; 255 | }// end function 256 | public function get ns() : NetStream 257 | { 258 | return _$ns; 259 | } 260 | public function get loadPercent() : Number 261 | { 262 | return _$ns.bytesLoaded / _$ns.bytesTotal; 263 | } 264 | public function get total() : Number 265 | { 266 | return _$ns.bytesTotal; 267 | } 268 | public function get loaded() : Number 269 | { 270 | return _$ns.bytesLoaded; 271 | } 272 | public function get time() : Number 273 | { 274 | var time:uint = Math.round(_$ns.time * 1000); 275 | return time; 276 | } 277 | public function set volume(param1:Number) : void 278 | { 279 | param1 = param1 > 1 ? (1) : (param1); 280 | param1 = param1 < 0 ? (0) : (param1); 281 | setVolume(param1); 282 | return; 283 | } 284 | public function set checkPolicyFile(check:Boolean):void{ 285 | this._$checkPolicyFile; 286 | } 287 | public function get id() : uint 288 | { 289 | return _$id; 290 | } 291 | public function get bufferTime() : int 292 | { 293 | return _$bufferTime; 294 | } 295 | public function set bufferTime(param1:int) : void 296 | { 297 | _$bufferTime = param1; 298 | _$ns.bufferTime = param1; 299 | return; 300 | } 301 | public function onMetaData(param1:Object) : void 302 | { 303 | dispatchEvent(new NSEvent(NSEvent.META_DATA, param1)); 304 | return; 305 | } 306 | public function onCuePoint(param1:Object) : void 307 | { 308 | trace("cue point" + param1); 309 | return; 310 | } 311 | public function onLastSecond(param1:Object):void 312 | { 313 | trace("last second" + param1); 314 | } 315 | public function onReallyFull(param1:NSEvent):void 316 | { 317 | this._$btimer.stop() 318 | } 319 | public function onXMPData(param1:Object):void{ 320 | return; 321 | } 322 | } 323 | } -------------------------------------------------------------------------------- /src/org/kanoha/video/NSComponent.as: -------------------------------------------------------------------------------- 1 | /******************* 2 | * Based on Tamaki Video Component 3 | * Modified by CQZ 4 | * 2011-9-3 Initial 5 | ********************/ 6 | package org.kanoha.video 7 | { 8 | import flash.events.Event; 9 | import flash.filters.ColorMatrixFilter; 10 | import flash.media.Video; 11 | 12 | import mx.core.UIComponent; 13 | import mx.events.ResizeEvent; 14 | 15 | import org.kanoha.events.*; 16 | public class NSComponent extends UIComponent 17 | { 18 | private var nss:Array; //netstreams 19 | private var ifs:Array; //videoinfo 20 | private var ofs:Array; //offset:uint 21 | private var bi:int; //buffer index 22 | private var pi:int;//play index 23 | private var _$volume:Number; 24 | private var _$video:Video; 25 | private var _$total:uint; 26 | private var _$state:String = NSComponent.UNLOAD; 27 | private var _$smoothing:Boolean; 28 | private var _$metaReader:NS; 29 | private var _$url:String; 30 | private var _$allowScreencap:Boolean = true; 31 | public static const UNLOAD:String = "unLoad"; 32 | public static const PLAY:String = "play"; 33 | public static const STOP:String = "stop"; 34 | public static const PAUSE:String = "pause"; 35 | public var p_x:int = 0; 36 | public var p_y:int = 0; 37 | public var nsBuf:Boolean=false; 38 | public function NSComponent() 39 | { 40 | super(); 41 | this._$video = new Video(); 42 | this.addChild(this._$video); 43 | this._$video.height = this.height; 44 | this._$video.width = this.width; 45 | this.addEventListener(ResizeEvent.RESIZE,resize); 46 | this.addEventListener(NMEvent.COMPLETE,onComplete); 47 | } 48 | public function resize(eve:Event):void 49 | { 50 | if(this._$video==null) 51 | return;//dont resize if video not inited 52 | if(this.p_x<=0 || this.p_y<=0){ 53 | this._$video.width = this.width; 54 | this._$video.height = this.height; 55 | }else{ 56 | if((this.width / this.height) >= (this.p_x / this.p_y)){ 57 | this._$video.height = this.height; 58 | this._$video.width = (this.height * this.p_x )/this.p_y; 59 | }else{ 60 | this._$video.width = this.width; 61 | this._$video.height = (this.width * this.p_y)/ this.p_x; 62 | } 63 | //Position Center 64 | this._$video.x = (this.width - this._$video.width)/2; 65 | this._$video.y = (this.height - this._$video.height)/2; 66 | } 67 | } 68 | public function init(ifv:Array,tv:uint):void 69 | { 70 | this.close(); 71 | this.resetAll(); 72 | this.ifs = ifv; 73 | this._$total = tv; 74 | initOffset(); 75 | createBuffer(); 76 | } 77 | public function initBySingleUrl(url:String):void 78 | { 79 | this.close(); 80 | this.resetAll(); 81 | this._$url = url; 82 | this._$metaReader = new NS(); 83 | this._$metaReader.addEventListener(NSEvent.META_DATA,onMetaData); 84 | this._$metaReader.loadV(url); 85 | this.addEventListener(NMEvent.META_LOADED,onMetaLoaded); 86 | } 87 | private function onMetaLoaded(eve:NMEvent):void 88 | { 89 | initOffset(); 90 | trace('loaded meta!'); 91 | createBuffer(); 92 | this.dispatchEvent(new NMEvent(NMEvent.URL_COMPLETE)); 93 | } 94 | private function initOffset():void 95 | { 96 | var co:uint = 0; 97 | for(var i:uint = 0;i < ifs.length;i++) 98 | { 99 | ofs[i] = co += VideoInfo(ifs[i]).length; 100 | } 101 | } 102 | private function createBuffer():void 103 | { 104 | if(ifs[++bi]) 105 | { 106 | var ns:NS = new NS(); 107 | var info:VideoInfo = VideoInfo(ifs[bi]); 108 | if(this._$allowScreencap){ 109 | ns.checkPolicyFile = true; 110 | } 111 | ns.id = info.id; 112 | ns.loadV(info.url); 113 | ns.addEventListener(NSEvent.META_DATA,onSingleMetaData); 114 | ns.addEventListener(NSEvent.STOP, onStop); 115 | ns.addEventListener(NSEvent.CHECK_FULL, onFull); 116 | ns.addEventListener(NSEvent.BUFFERING,onBuff); 117 | ns.addEventListener(NSEvent.PLAYING,onPlaying); 118 | ns.addEventListener(NSEvent.FILE_EMPTY,onNotFound); 119 | ns.volume = this._$volume; 120 | nss.push(ns); 121 | } 122 | } 123 | private function changeNS():void 124 | { 125 | if(pi >= 0) 126 | { 127 | if(!nss[pi]) 128 | return; 129 | NS(nss[pi]).stopV(); 130 | } 131 | if(nss[pi+1]) 132 | { 133 | NS(nss[++pi]).playV(); 134 | NS(nss[pi]).stopV(); 135 | NS(nss[pi]).playV(); 136 | this.attachVideo(_$video); 137 | } 138 | } 139 | private function onFull(eve:NSEvent):void 140 | { 141 | if(this.state != NSComponent.UNLOAD) 142 | { 143 | if(nss[bi]!=null){ 144 | NS(nss[bi]).removeEventListener(NSEvent.BUFFERING,onBuff); 145 | NS(nss[bi]).removeEventListener(NSEvent.CHECK_FULL,onFull); 146 | } 147 | createBuffer(); 148 | } 149 | } 150 | private function onStop(eve:NSEvent):void 151 | { 152 | if(pi != ifs.length-1) 153 | changeNS(); 154 | else 155 | { 156 | this.dispatchEvent(new NMEvent(NMEvent.COMPLETE)); 157 | } 158 | } 159 | private function onBuff(eve:NSEvent):void 160 | { 161 | var current:Number; 162 | var next:Number 163 | if(bi == 0) 164 | { 165 | next = ofs[bi]/_$total; 166 | current = Number(eve.info) * next; 167 | this.dispatchEvent(new NMEvent(NMEvent.PROGRESS,current)); 168 | } 169 | else 170 | { 171 | var pre:Number = ofs[bi-1]/_$total; 172 | next = ofs[bi]/_$total; 173 | current = pre + Number(eve.info) * (next - pre); 174 | this.dispatchEvent(new NMEvent(NMEvent.PROGRESS,current)); 175 | } 176 | } 177 | private function onPlaying(eve:NSEvent):void 178 | { 179 | if(pi == 0) 180 | this.dispatchEvent(new NMEvent(NMEvent.PLAY_HEAD_UPDATE,uint(eve.info))); 181 | else 182 | { 183 | this.dispatchEvent(new NMEvent(NMEvent.PLAY_HEAD_UPDATE,uint(ofs[pi-1]+uint(eve.info)))); 184 | } 185 | } 186 | private function onNotFound(eve:NSEvent):void 187 | { 188 | this.dispatchEvent(new NMEvent(NMEvent.VIDEO_NOT_FOUND)) 189 | } 190 | private function getPIByTime(time:uint):uint 191 | { 192 | var i:uint = 0; 193 | var pre:uint = 0; 194 | for(;i pre && time <= ofs[i]) 197 | break; 198 | pre = ofs[i++]; 199 | } 200 | return i; 201 | } 202 | private function seekInPart(time:uint,si:uint):void 203 | { 204 | var ptime:uint; 205 | if (si == 0) 206 | ptime = time; 207 | else 208 | ptime = time - ofs[si-1]; 209 | NS(nss[pi]).seekV(ptime); 210 | this.play(); 211 | } 212 | private function attachVideo(v:Video):void 213 | { 214 | //v.clear(); 215 | v.attachNetStream(NS(nss[pi]).ns); 216 | } 217 | private function onSingleMetaData(eve:NSEvent):void{ 218 | if(!nsBuf){ 219 | nsBuf = true; 220 | }else{ 221 | return; 222 | } 223 | if(eve.info.width!=null && eve.info.height!=null){ 224 | this.p_x = eve.info.width; 225 | this.p_y = eve.info.height; 226 | this.resize(null); 227 | } 228 | if(nss != null && nss[bi]!=null ) 229 | NS(this.nss[bi]).removeEventListener(NMEvent.META_LOADED,onSingleMetaData); 230 | this.dispatchEvent(new NMEvent(NMEvent.META_LOADED,eve.info)); 231 | } 232 | private function onMetaData(eve:NSEvent):void 233 | { 234 | if(eve.info.width!=null && eve.info.height!=null){ 235 | this.p_x = eve.info.width; 236 | this.p_y = eve.info.height; 237 | this.resize(null); 238 | } 239 | this._$total = eve.info.duration * 1000; 240 | this.ifs = new Array; 241 | this.ifs.push(new VideoInfo(this._$url,eve.info.duration *1000)); 242 | this._$metaReader.removeEventListener(NSEvent.META_DATA,onMetaData); 243 | this._$metaReader.closeV(); 244 | this._$metaReader = null; 245 | this.dispatchEvent(new NMEvent(NMEvent.META_LOADED,eve.info)); 246 | } 247 | private function resetAll():void 248 | { 249 | if(nss&&nss[bi]) 250 | { 251 | NS(nss[bi]).removeEventListener(NSEvent.BUFFERING,onBuff); 252 | NS(nss[bi]).removeEventListener(NSEvent.CHECK_FULL,onFull); 253 | } 254 | this.bi = -1; 255 | this.pi = -1; 256 | this._$volume = 0.7; 257 | this.nss = new Array(); 258 | this.ofs = new Array(); 259 | this._$state = NSComponent.UNLOAD; 260 | } 261 | private function onComplete(eve:NMEvent):void 262 | { 263 | this.stop(); 264 | this.play(); 265 | this.stop(); 266 | this.dispatchEvent(new NMEvent(NMEvent.REFRESH)); 267 | } 268 | /*** Handlers ***/ 269 | public function seek(time:uint):Boolean 270 | { 271 | if(this._$state == NSComponent.STOP||this._$state == NSComponent.UNLOAD) 272 | return false; 273 | var si:uint = this.getPIByTime(time); 274 | if(si >= nss.length) 275 | si = nss.length-1; 276 | if(si == pi) 277 | { 278 | this.seekInPart(time,si); 279 | } 280 | else 281 | { 282 | this.stop(); 283 | pi = si; 284 | this.attachVideo(this._$video); 285 | this.seekInPart(time,si); 286 | } 287 | return true; 288 | } 289 | public function play():Boolean 290 | { 291 | if(this._$state == NSComponent.PLAY) 292 | return false; 293 | if(pi == -1) 294 | changeNS(); 295 | else 296 | NS(nss[pi]).playV(); 297 | this.dispatchEvent(new NMEvent(NMEvent.STATE_CHANGE,"play")); 298 | this._$state = NSComponent.PLAY; 299 | return true; 300 | } 301 | public function pause():Boolean 302 | { 303 | if(this._$state != NSComponent.PLAY) 304 | return false; 305 | if(nss==null || nss[pi]==null){ 306 | //not prepared 307 | return false; 308 | } 309 | NS(nss[pi]).pauseV(); 310 | this.dispatchEvent(new NMEvent(NMEvent.STATE_CHANGE,"pause")); 311 | this._$state = NSComponent.PAUSE; 312 | return true; 313 | } 314 | public function stop():Boolean 315 | { 316 | if(this._$state == NSComponent.STOP||this._$state==NSComponent.UNLOAD) 317 | return false 318 | for(var i:uint = 0;i 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnCommentOnSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnFullScreenSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnMuteSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnPauseSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnPlaySkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnSidebarOffSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnSidebarOnSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnSmallScreenSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnStopSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/btnVolumeSkin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/skins/containerBar.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | --------------------------------------------------------------------------------