PageRenderTime 150ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/codeazur/as3swf/SWFTimelineContainer.as

http://github.com/claus/as3swf
ActionScript | 532 lines | 462 code | 42 blank | 28 comment | 69 complexity | e7b34f644ce83bf6fefd8b1b8f89b4c2 MD5 | raw file
Possible License(s): MIT
  1. package com.codeazur.as3swf
  2. {
  3. import com.codeazur.as3swf.data.SWFFrameLabel;
  4. import com.codeazur.as3swf.data.SWFRawTag;
  5. import com.codeazur.as3swf.data.SWFRecordHeader;
  6. import com.codeazur.as3swf.data.SWFScene;
  7. import com.codeazur.as3swf.data.consts.SoundCompression;
  8. import com.codeazur.as3swf.events.SWFErrorEvent;
  9. import com.codeazur.as3swf.events.SWFEventDispatcher;
  10. import com.codeazur.as3swf.events.SWFProgressEvent;
  11. import com.codeazur.as3swf.events.SWFWarningEvent;
  12. import com.codeazur.as3swf.factories.ISWFTagFactory;
  13. import com.codeazur.as3swf.factories.SWFTagFactory;
  14. import com.codeazur.as3swf.tags.IDefinitionTag;
  15. import com.codeazur.as3swf.tags.IDisplayListTag;
  16. import com.codeazur.as3swf.tags.ITag;
  17. import com.codeazur.as3swf.tags.TagDefineMorphShape;
  18. import com.codeazur.as3swf.tags.TagDefineSceneAndFrameLabelData;
  19. import com.codeazur.as3swf.tags.TagEnd;
  20. import com.codeazur.as3swf.tags.TagFrameLabel;
  21. import com.codeazur.as3swf.tags.TagJPEGTables;
  22. import com.codeazur.as3swf.tags.TagPlaceObject;
  23. import com.codeazur.as3swf.tags.TagPlaceObject2;
  24. import com.codeazur.as3swf.tags.TagPlaceObject3;
  25. import com.codeazur.as3swf.tags.TagRemoveObject;
  26. import com.codeazur.as3swf.tags.TagRemoveObject2;
  27. import com.codeazur.as3swf.tags.TagSetBackgroundColor;
  28. import com.codeazur.as3swf.tags.TagShowFrame;
  29. import com.codeazur.as3swf.tags.TagSoundStreamBlock;
  30. import com.codeazur.as3swf.tags.TagSoundStreamHead;
  31. import com.codeazur.as3swf.tags.TagSoundStreamHead2;
  32. import com.codeazur.as3swf.timeline.Frame;
  33. import com.codeazur.as3swf.timeline.FrameObject;
  34. import com.codeazur.as3swf.timeline.Layer;
  35. import com.codeazur.as3swf.timeline.LayerStrip;
  36. import com.codeazur.as3swf.timeline.Scene;
  37. import com.codeazur.as3swf.timeline.SoundStream;
  38. import com.codeazur.utils.StringUtils;
  39. import flash.display.Sprite;
  40. import flash.events.Event;
  41. import flash.utils.ByteArray;
  42. import flash.utils.Dictionary;
  43. import flash.utils.Endian;
  44. import flash.utils.getTimer;
  45. public class SWFTimelineContainer extends SWFEventDispatcher
  46. {
  47. // We're just being lazy here.
  48. public static var TIMEOUT:int = 50;
  49. public static var AUTOBUILD_LAYERS:Boolean = false;
  50. public static var EXTRACT_SOUND_STREAM:Boolean = true;
  51. protected var _tags:Vector.<ITag>;
  52. protected var _tagsRaw:Vector.<SWFRawTag>;
  53. protected var _dictionary:Dictionary;
  54. protected var _scenes:Vector.<Scene>;
  55. protected var _frames:Vector.<Frame>;
  56. protected var _layers:Vector.<Layer>;
  57. protected var _soundStream:SoundStream;
  58. protected var currentFrame:Frame;
  59. protected var frameLabels:Dictionary;
  60. protected var hasSoundStream:Boolean = false;
  61. protected var enterFrameProvider:Sprite;
  62. protected var eof:Boolean;
  63. protected var _tmpData:SWFData;
  64. protected var _tmpVersion:uint;
  65. protected var _tmpTagIterator:int = 0;
  66. protected var _tagFactory:ISWFTagFactory;
  67. internal var rootTimelineContainer:SWFTimelineContainer;
  68. public var backgroundColor:uint = 0xffffff;
  69. public var jpegTablesTag:TagJPEGTables;
  70. public function SWFTimelineContainer()
  71. {
  72. _tags = new Vector.<ITag>();
  73. _tagsRaw = new Vector.<SWFRawTag>();
  74. _dictionary = new Dictionary();
  75. _scenes = new Vector.<Scene>();
  76. _frames = new Vector.<Frame>();
  77. _layers = new Vector.<Layer>();
  78. _tagFactory = new SWFTagFactory();
  79. rootTimelineContainer = this;
  80. enterFrameProvider = new Sprite();
  81. }
  82. public function get tags():Vector.<ITag> { return _tags; }
  83. public function get tagsRaw():Vector.<SWFRawTag> { return _tagsRaw; }
  84. public function get dictionary():Dictionary { return _dictionary; }
  85. public function get scenes():Vector.<Scene> { return _scenes; }
  86. public function get frames():Vector.<Frame> { return _frames; }
  87. public function get layers():Vector.<Layer> { return _layers; }
  88. public function get soundStream():SoundStream { return _soundStream; }
  89. public function get tagFactory():ISWFTagFactory { return _tagFactory; }
  90. public function set tagFactory(value:ISWFTagFactory):void { _tagFactory = value; }
  91. public function getCharacter(characterId:uint):IDefinitionTag {
  92. var tagIndex:int = rootTimelineContainer.dictionary[characterId];
  93. if(tagIndex >= 0 && tagIndex < rootTimelineContainer.tags.length) {
  94. return rootTimelineContainer.tags[tagIndex] as IDefinitionTag;
  95. }
  96. return null;
  97. }
  98. public function parseTags(data:SWFData, version:uint):void {
  99. var tag:ITag;
  100. parseTagsInit(data, version);
  101. while ((tag = parseTag(data)) && tag.type != TagEnd.TYPE) {};
  102. parseTagsFinalize();
  103. }
  104. public function parseTagsAsync(data:SWFData, version:uint):void {
  105. parseTagsInit(data, version);
  106. enterFrameProvider.addEventListener(Event.ENTER_FRAME, parseTagsAsyncHandler);
  107. }
  108. protected function parseTagsAsyncHandler(event:Event):void {
  109. enterFrameProvider.removeEventListener(Event.ENTER_FRAME, parseTagsAsyncHandler);
  110. if(dispatchEvent(new SWFProgressEvent(SWFProgressEvent.PROGRESS, _tmpData.position, _tmpData.length, false, true))) {
  111. parseTagsAsyncInternal();
  112. }
  113. }
  114. protected function parseTagsAsyncInternal():void {
  115. var tag:ITag;
  116. var time:int = getTimer();
  117. while ((tag = parseTag(_tmpData, true)) && tag.type != TagEnd.TYPE) {
  118. if((getTimer() - time) > TIMEOUT) {
  119. enterFrameProvider.addEventListener(Event.ENTER_FRAME, parseTagsAsyncHandler);
  120. return;
  121. }
  122. }
  123. parseTagsFinalize();
  124. if(eof) {
  125. dispatchEvent(new SWFErrorEvent(SWFErrorEvent.ERROR, SWFErrorEvent.REASON_EOF));
  126. } else {
  127. dispatchEvent(new SWFProgressEvent(SWFProgressEvent.PROGRESS, _tmpData.position, _tmpData.length));
  128. dispatchEvent(new SWFProgressEvent(SWFProgressEvent.COMPLETE, _tmpData.position, _tmpData.length));
  129. }
  130. }
  131. protected function parseTagsInit(data:SWFData, version:uint):void {
  132. tags.length = 0;
  133. frames.length = 0;
  134. layers.length = 0;
  135. _dictionary = new Dictionary();
  136. currentFrame = new Frame();
  137. frameLabels = new Dictionary();
  138. hasSoundStream = false;
  139. _tmpData = data;
  140. _tmpVersion = version;
  141. }
  142. protected function parseTag(data:SWFData, async:Boolean = false):ITag {
  143. var pos:uint = data.position;
  144. // Bail out if eof
  145. eof = (pos >= data.length);
  146. if(eof) {
  147. trace("WARNING: end of file encountered, no end tag.");
  148. return null;
  149. }
  150. var tagRaw:SWFRawTag = data.readRawTag();
  151. var tagHeader:SWFRecordHeader = tagRaw.header;
  152. var tag:ITag = tagFactory.create(tagHeader.type);
  153. try {
  154. if(tag is SWFTimelineContainer) {
  155. var timelineContainer:SWFTimelineContainer = tag as SWFTimelineContainer;
  156. // Currently, the only SWFTimelineContainer (other than the SWF root
  157. // itself) is TagDefineSprite (MovieClips have their own timeline).
  158. // Inject the current tag factory there.
  159. timelineContainer.tagFactory = tagFactory;
  160. timelineContainer.rootTimelineContainer = this;
  161. }
  162. // Parse tag
  163. tag.parse(data, tagHeader.contentLength, _tmpVersion, async);
  164. } catch(e:Error) {
  165. // If we get here there was a problem parsing this particular tag.
  166. // Corrupted SWF, possible SWF exploit, or obfuscated SWF.
  167. // TODO: register errors and warnings
  168. trace("WARNING: parse error: " + e.message + ", Tag: " + tag.name + ", Index: " + tags.length);
  169. throw(e);
  170. }
  171. // Register tag
  172. tags.push(tag);
  173. tagsRaw.push(tagRaw);
  174. // Build dictionary and display list etc
  175. processTag(tag);
  176. // Adjust position (just in case the parser under- or overflows)
  177. if(data.position != pos + tagHeader.tagLength) {
  178. var index:uint = tags.length - 1;
  179. var excessBytes:int = data.position - (pos + tagHeader.tagLength);
  180. var eventType:String = (excessBytes < 0) ? SWFWarningEvent.UNDERFLOW : SWFWarningEvent.OVERFLOW;
  181. var eventData:Object = {
  182. pos: pos,
  183. bytes: (excessBytes < 0) ? -excessBytes : excessBytes
  184. };
  185. if(rootTimelineContainer == this) {
  186. trace("WARNING: excess bytes: " + excessBytes + ", " +
  187. "Tag: " + tag.name + ", " +
  188. "Index: " + index
  189. );
  190. } else {
  191. eventData.indexRoot = rootTimelineContainer.tags.length;
  192. trace("WARNING: excess bytes: " + excessBytes + ", " +
  193. "Tag: " + tag.name + ", " +
  194. "Index: " + index + ", " +
  195. "IndexRoot: " + eventData.indexRoot
  196. );
  197. }
  198. var event:SWFWarningEvent = new SWFWarningEvent(eventType, index, eventData, false, true);
  199. var cancelled:Boolean = !dispatchEvent(event);
  200. if (cancelled) {
  201. tag = null;
  202. }
  203. data.position = pos + tagHeader.tagLength;
  204. }
  205. return tag;
  206. }
  207. protected function parseTagsFinalize():void {
  208. if(soundStream && soundStream.data.length == 0) {
  209. _soundStream = null;
  210. }
  211. if(AUTOBUILD_LAYERS) {
  212. // TODO: This needs to go into processTags()
  213. buildLayers();
  214. }
  215. }
  216. public function publishTags(data:SWFData, version:uint):void {
  217. var tag:ITag;
  218. var tagRaw:SWFRawTag;
  219. for (var i:uint = 0; i < tags.length; i++) {
  220. tag = tags[i];
  221. tagRaw = (i < tagsRaw.length) ? tagsRaw[i] : null;
  222. publishTag(data, tag, tagRaw, version);
  223. }
  224. }
  225. public function publishTagsAsync(data:SWFData, version:uint):void {
  226. _tmpData = data;
  227. _tmpVersion = version;
  228. _tmpTagIterator = 0;
  229. enterFrameProvider.addEventListener(Event.ENTER_FRAME, publishTagsAsyncHandler);
  230. }
  231. protected function publishTagsAsyncHandler(event:Event):void {
  232. enterFrameProvider.removeEventListener(Event.ENTER_FRAME, publishTagsAsyncHandler);
  233. if(dispatchEvent(new SWFProgressEvent(SWFProgressEvent.PROGRESS, _tmpTagIterator, tags.length))) {
  234. publishTagsAsyncInternal();
  235. }
  236. }
  237. protected function publishTagsAsyncInternal():void {
  238. var tag:ITag;
  239. var tagRaw:SWFRawTag;
  240. var time:int = getTimer();
  241. do {
  242. tag = (_tmpTagIterator < tags.length) ? tags[_tmpTagIterator] : null;
  243. tagRaw = (_tmpTagIterator < tagsRaw.length) ? tagsRaw[_tmpTagIterator] : null;
  244. publishTag(_tmpData, tag, tagRaw, _tmpVersion);
  245. _tmpTagIterator++;
  246. if((getTimer() - time) > TIMEOUT) {
  247. enterFrameProvider.addEventListener(Event.ENTER_FRAME, publishTagsAsyncHandler);
  248. return;
  249. }
  250. }
  251. while (tag.type != TagEnd.TYPE);
  252. dispatchEvent(new SWFProgressEvent(SWFProgressEvent.PROGRESS, _tmpTagIterator, tags.length));
  253. dispatchEvent(new SWFProgressEvent(SWFProgressEvent.COMPLETE, _tmpTagIterator, tags.length));
  254. }
  255. public function publishTag(data:SWFData, tag:ITag, rawTag:SWFRawTag, version:uint):void {
  256. try {
  257. tag.publish(data, version);
  258. }
  259. catch (e:Error) {
  260. trace("WARNING: publish error: " + e.message + " (tag: " + tag.name + ")");
  261. if(rawTag) {
  262. rawTag.publish(data);
  263. } else {
  264. trace("FATAL: publish error: No raw tag fallback");
  265. }
  266. }
  267. }
  268. protected function processTag(tag:ITag):void {
  269. var currentTagIndex:uint = tags.length - 1;
  270. if(tag is IDefinitionTag) {
  271. processDefinitionTag(tag as IDefinitionTag, currentTagIndex);
  272. return;
  273. } else if(tag is IDisplayListTag) {
  274. processDisplayListTag(tag as IDisplayListTag, currentTagIndex);
  275. return;
  276. }
  277. switch(tag.type) {
  278. // Frame labels and scenes
  279. case TagFrameLabel.TYPE:
  280. case TagDefineSceneAndFrameLabelData.TYPE:
  281. processFrameLabelTag(tag, currentTagIndex);
  282. break;
  283. // Sound stream
  284. case TagSoundStreamHead.TYPE:
  285. case TagSoundStreamHead2.TYPE:
  286. case TagSoundStreamBlock.TYPE:
  287. if(EXTRACT_SOUND_STREAM) {
  288. processSoundStreamTag(tag, currentTagIndex);
  289. }
  290. break;
  291. // Background color
  292. case TagSetBackgroundColor.TYPE:
  293. processBackgroundColorTag(tag as TagSetBackgroundColor, currentTagIndex);
  294. break;
  295. // Global JPEG Table
  296. case TagJPEGTables.TYPE:
  297. processJPEGTablesTag(tag as TagJPEGTables, currentTagIndex);
  298. break;
  299. }
  300. }
  301. protected function processDefinitionTag(tag:IDefinitionTag, currentTagIndex:uint):void {
  302. if(tag.characterId > 0) {
  303. // Register definition tag in dictionary
  304. // key: character id
  305. // value: definition tag index
  306. dictionary[tag.characterId] = currentTagIndex;
  307. // Register character id in the current frame's character array
  308. currentFrame.characters.push(tag.characterId);
  309. }
  310. }
  311. protected function processDisplayListTag(tag:IDisplayListTag, currentTagIndex:uint):void {
  312. switch(tag.type) {
  313. case TagShowFrame.TYPE:
  314. currentFrame.tagIndexEnd = currentTagIndex;
  315. if(currentFrame.label == null && frameLabels[currentFrame.frameNumber]) {
  316. currentFrame.label = frameLabels[currentFrame.frameNumber];
  317. }
  318. frames.push(currentFrame);
  319. currentFrame = currentFrame.clone();
  320. currentFrame.frameNumber = frames.length;
  321. currentFrame.tagIndexStart = currentTagIndex + 1;
  322. break;
  323. case TagPlaceObject.TYPE:
  324. case TagPlaceObject2.TYPE:
  325. case TagPlaceObject3.TYPE:
  326. currentFrame.placeObject(currentTagIndex, tag as TagPlaceObject);
  327. break;
  328. case TagRemoveObject.TYPE:
  329. case TagRemoveObject2.TYPE:
  330. currentFrame.removeObject(tag as TagRemoveObject);
  331. break;
  332. }
  333. }
  334. protected function processFrameLabelTag(tag:ITag, currentTagIndex:uint):void {
  335. switch(tag.type) {
  336. case TagDefineSceneAndFrameLabelData.TYPE:
  337. var tagSceneAndFrameLabelData:TagDefineSceneAndFrameLabelData = tag as TagDefineSceneAndFrameLabelData;
  338. var i:uint;
  339. for(i = 0; i < tagSceneAndFrameLabelData.frameLabels.length; i++) {
  340. var frameLabel:SWFFrameLabel = tagSceneAndFrameLabelData.frameLabels[i] as SWFFrameLabel;
  341. frameLabels[frameLabel.frameNumber] = frameLabel.name;
  342. }
  343. for(i = 0; i < tagSceneAndFrameLabelData.scenes.length; i++) {
  344. var scene:SWFScene = tagSceneAndFrameLabelData.scenes[i] as SWFScene;
  345. scenes.push(new Scene(scene.offset, scene.name));
  346. }
  347. break;
  348. case TagFrameLabel.TYPE:
  349. var tagFrameLabel:TagFrameLabel = tag as TagFrameLabel;
  350. currentFrame.label = tagFrameLabel.frameName;
  351. break;
  352. }
  353. }
  354. protected function processSoundStreamTag(tag:ITag, currentTagIndex:uint):void {
  355. switch(tag.type) {
  356. case TagSoundStreamHead.TYPE:
  357. case TagSoundStreamHead2.TYPE:
  358. var tagSoundStreamHead:TagSoundStreamHead = tag as TagSoundStreamHead;
  359. _soundStream = new SoundStream();
  360. soundStream.compression = tagSoundStreamHead.streamSoundCompression;
  361. soundStream.rate = tagSoundStreamHead.streamSoundRate;
  362. soundStream.size = tagSoundStreamHead.streamSoundSize;
  363. soundStream.type = tagSoundStreamHead.streamSoundType;
  364. soundStream.numFrames = 0;
  365. soundStream.numSamples = 0;
  366. break;
  367. case TagSoundStreamBlock.TYPE:
  368. if(soundStream != null) {
  369. if(!hasSoundStream) {
  370. hasSoundStream = true;
  371. soundStream.startFrame = currentFrame.frameNumber;
  372. }
  373. var tagSoundStreamBlock:TagSoundStreamBlock = tag as TagSoundStreamBlock;
  374. var soundData:ByteArray = tagSoundStreamBlock.soundData;
  375. soundData.endian = Endian.LITTLE_ENDIAN;
  376. soundData.position = 0;
  377. switch(soundStream.compression) {
  378. case SoundCompression.ADPCM: // ADPCM
  379. // TODO
  380. break;
  381. case SoundCompression.MP3: // MP3
  382. var numSamples:uint = soundData.readUnsignedShort();
  383. var seekSamples:int = soundData.readShort();
  384. if(numSamples > 0) {
  385. soundStream.numSamples += numSamples;
  386. soundStream.data.writeBytes(soundData, 4);
  387. }
  388. break;
  389. }
  390. soundStream.numFrames++;
  391. }
  392. break;
  393. }
  394. }
  395. protected function processBackgroundColorTag(tag:TagSetBackgroundColor, currentTagIndex:uint):void {
  396. backgroundColor = tag.color;
  397. }
  398. protected function processJPEGTablesTag(tag:TagJPEGTables, currentTagIndex:uint):void {
  399. jpegTablesTag = tag;
  400. }
  401. public function buildLayers():void {
  402. var i:uint;
  403. var depth:String;
  404. var depthInt:uint;
  405. var depths:Dictionary = new Dictionary();
  406. var depthsAvailable:Array = [];
  407. for(i = 0; i < frames.length; i++) {
  408. var frame:Frame = frames[i];
  409. for(depth in frame.objects) {
  410. depthInt = parseInt(depth);
  411. if(depthsAvailable.indexOf(depthInt) > -1) {
  412. (depths[depth] as Array).push(frame.frameNumber);
  413. } else {
  414. depths[depth] = [frame.frameNumber];
  415. depthsAvailable.push(depthInt);
  416. }
  417. }
  418. }
  419. depthsAvailable.sort(Array.NUMERIC);
  420. for(i = 0; i < depthsAvailable.length; i++) {
  421. var layer:Layer = new Layer(depthsAvailable[i], frames.length);
  422. var frameIndices:Array = depths[depthsAvailable[i].toString()];
  423. var frameIndicesLen:uint = frameIndices.length;
  424. if(frameIndicesLen > 0) {
  425. var curStripType:uint = LayerStrip.TYPE_EMPTY;
  426. var startFrameIndex:uint = uint.MAX_VALUE;
  427. var endFrameIndex:uint = uint.MAX_VALUE;
  428. for(var j:uint = 0; j < frameIndicesLen; j++) {
  429. var curFrameIndex:uint = frameIndices[j];
  430. var curFrameObject:FrameObject = frames[curFrameIndex].objects[layer.depth] as FrameObject;
  431. if(curFrameObject.isKeyframe) {
  432. // a keyframe marks the start of a new strip: save current strip
  433. layer.appendStrip(curStripType, startFrameIndex, endFrameIndex);
  434. // set start of new strip
  435. startFrameIndex = curFrameIndex;
  436. // evaluate type of new strip (motion tween detection see below)
  437. curStripType = (getCharacter(curFrameObject.characterId) is TagDefineMorphShape) ? LayerStrip.TYPE_SHAPETWEEN : LayerStrip.TYPE_STATIC;
  438. } else if(curStripType == LayerStrip.TYPE_STATIC && curFrameObject.lastModifiedAtIndex > 0) {
  439. // if one of the matrices of an object in a static strip is
  440. // modified at least once, we are dealing with a motion tween:
  441. curStripType = LayerStrip.TYPE_MOTIONTWEEN;
  442. }
  443. // update the end of the strip
  444. endFrameIndex = curFrameIndex;
  445. }
  446. layer.appendStrip(curStripType, startFrameIndex, endFrameIndex);
  447. }
  448. _layers.push(layer);
  449. }
  450. for(i = 0; i < frames.length; i++) {
  451. var frameObjs:Dictionary = frames[i].objects;
  452. for(depth in frameObjs) {
  453. FrameObject(frameObjs[depth]).layer = depthsAvailable.indexOf(parseInt(depth));
  454. }
  455. }
  456. }
  457. public function toString(indent:uint = 0, flags:uint = 0):String {
  458. var i:uint;
  459. var str:String = "";
  460. if (tags.length > 0) {
  461. str += "\n" + StringUtils.repeat(indent + 2) + "Tags:";
  462. for (i = 0; i < tags.length; i++) {
  463. str += "\n" + tags[i].toString(indent + 4);
  464. }
  465. }
  466. if ((flags & SWF.TOSTRING_FLAG_TIMELINE_STRUCTURE) != 0) {
  467. if (scenes.length > 0) {
  468. str += "\n" + StringUtils.repeat(indent + 2) + "Scenes:";
  469. for (i = 0; i < scenes.length; i++) {
  470. str += "\n" + scenes[i].toString(indent + 4);
  471. }
  472. }
  473. if (frames.length > 0) {
  474. str += "\n" + StringUtils.repeat(indent + 2) + "Frames:";
  475. for (i = 0; i < frames.length; i++) {
  476. str += "\n" + frames[i].toString(indent + 4);
  477. }
  478. }
  479. if (layers.length > 0) {
  480. str += "\n" + StringUtils.repeat(indent + 2) + "Layers:";
  481. for (i = 0; i < layers.length; i++) {
  482. str += "\n" + StringUtils.repeat(indent + 4) +
  483. "[" + i + "] " + layers[i].toString(indent + 4);
  484. }
  485. }
  486. }
  487. return str;
  488. }
  489. }
  490. }