package { import flash.utils.Timer; import flash.events.*; import flash.utils.ByteArray; import org.partty.mxml.Terminal; public class Captty extends EventDispatcher { public static const FRAME_OUTPUT:uint = 0; public static const FRAME_WINDOW_SIZE:uint = 1; public static const FRAME_HEADER_SIZE:uint = 4+1+2; public static const BLOCK_UNCOMPRESSED_FRAMES:uint = 0; public static const BLOCK_COMPRESSED_FRAMES:uint = 1; public static const BLOCK_HEADER_SIZE:uint = 4+1+2; public static const MAX_BLOCK_SIZE:uint = 1<<19; public static const SEEK_EVENT:String = "seekEvent"; private var _source:ByteArray; private var _index:Array; private var _block_pos:uint; private var _block:ByteArray; private var _frame:ByteArray; private var _outputTimer:Timer; private var _windowWizeTimer:Timer; private var _currentTimer:Timer; private var _speed:Number; private var _skipMode:Boolean; private var _skipBlock:Boolean; private var _terminal:Terminal; public function Captty(source:ByteArray, terminal:Terminal):void { _source = source; _source.endian = "littleEndian"; _terminal = terminal; _index = new Array(); create_index(); _block_pos = 0; _block = new ByteArray(); _frame = new ByteArray(); _block.endian = "littleEndian"; _frame.endian = "littleEndian"; _speed = 1.0; _outputTimer = new Timer(0, 1); _outputTimer.addEventListener(TimerEvent.TIMER, playOutput); _windowWizeTimer = new Timer(0, 1); _windowWizeTimer.addEventListener(TimerEvent.TIMER, playWindowSize); _skipMode = false; _skipBlock = false; } private function create_index():void { var source:ByteArray = _source; var index:Array = _index; while(source.bytesAvailable >= BLOCK_HEADER_SIZE) { var fp:uint = source.position; var btdiff:uint = source.readUnsignedShort(); var bflag:uint = source.readUnsignedByte(); var blength:uint = source.readUnsignedInt(); /* if(source.bytesAvailable < blength) { throw new EOFError("invalid block length"); } if(blength > MAX_BLOCK_SIZE) { throw new RangeError("invalid block length"); } */ index.push( new IndexInfo(btdiff, fp) ); source.position += blength; } source.position = 0; } private function readBlock():Boolean { var source:ByteArray = _source; var block:ByteArray = _block; do { if(source.bytesAvailable < BLOCK_HEADER_SIZE) { return false; } var btdiff:uint = source.readUnsignedShort(); var bflag:uint = source.readUnsignedByte(); var blength:uint = source.readUnsignedInt(); if(source.bytesAvailable < blength) { return false; } source.readBytes(block, 0, blength); _block_pos += 1; dispatchEvent(new Event(SEEK_EVENT)); switch(bflag) { case BLOCK_COMPRESSED_FRAMES: block.uncompress(); break; case BLOCK_UNCOMPRESSED_FRAMES: break; default: // unsupported block continue; } } while(false); return true; } public function play():void { if(_currentTimer) { _currentTimer.start(); } else { _terminal.clear(); playFrame(); } } public function stop():void { if(_currentTimer) { _currentTimer.stop(); } } public function togglePause():void { if(_currentTimer) { if(_currentTimer.running) { _currentTimer.stop(); } else { _currentTimer.start(); } } else { playFrame(); } } private function playFrame():void { var block:ByteArray = _block; do { if(_skipBlock) { block.length = 0; } if(block.bytesAvailable < FRAME_HEADER_SIZE) { if(!_skipBlock) { _skipMode = false; } else { _skipBlock = false; } block.length = 0; if( !readBlock() ) { _currentTimer = null; dispatchEvent(new Event(Event.COMPLETE)); trace("captty finished"); return; } } var frame:ByteArray = _frame; var ftdiff:uint = block.readUnsignedInt(); var fflag:uint = block.readUnsignedByte(); var fdata_length:uint = block.readUnsignedShort(); /* if(block.bytesAvailable < fdata_length) { throw new EOFError("invalid frame length"); } */ frame.length = 0; block.readBytes(frame, 0, fdata_length); var timer:Timer; switch(fflag) { case FRAME_OUTPUT: timer = _outputTimer; break; case FRAME_WINDOW_SIZE: if(frame.length != 4) { dispatchEvent( new IOErrorEvent( IOErrorEvent.IO_ERROR, false, false, "invalid window size frame" ) ); return; } timer = _windowWizeTimer; break; default: trace("captty skip unsupported frame"); continue; // skip this frame } _currentTimer = timer; if(_skipMode) { fireCurrentTimer(); } else { // microseconds to milliseconds setCurrentTimerDelay(ftdiff/1000/_speed); } timer.reset(); timer.start(); return; } while(true); } private function playOutput(event:TimerEvent = null):void { if(!_skipBlock) { _terminal.writeBytes(_frame); _terminal.refresh(); } playFrame(); } private function playWindowSize(event:TimerEvent = null):void { if(!_skipBlock) { var rows:uint = _frame.readUnsignedShort(); var cols:uint = _frame.readUnsignedShort(); _terminal.resize(cols, rows); //_terminal.refresh(); } playFrame(); } public function speedUp():void { _speed *= 2; fireCurrentTimer(); } public function speedDown():void { _speed /= 2; } public function speedReset():void { _speed = 1.0; } [Bindable] public function get speed():Number { return _speed; } public function set speed(num:Number):void { if(num == 0.0) { num = 0.000001; } if(_currentTimer && _currentTimer.running) { var nd:Number = _currentTimer.delay * _speed / num; if(nd == 0) { fireCurrentTimer(); } else { _currentTimer.delay = nd; } } _speed = num; } public function get maxIndex():uint { return _index.length; } public function get currentIndex():uint { return _block_pos <= 0 ? 0 : _block_pos - 1; } public function set currentIndex(pos:uint):void { if(pos < 1) { _block_pos = 0; } else { _block_pos = pos - 1; _skipMode = true; } _source.position = _index[_block_pos].seekpos; _skipBlock = true; _terminal.clear(); fireCurrentTimer(); } public function skipBack():void { if( _block_pos <= 3 ) { currentIndex = 0; } else { currentIndex = _block_pos - 2; } } public function skipForward():void { _skipMode = true; fireCurrentTimer(); } public function rewind():void { _block.length = 0; _source.position = 0; if(_currentTimer) { _currentTimer.stop(); _currentTimer = null; } _terminal.clear(); play(); } public function get running():Boolean { return _outputTimer.running || _windowWizeTimer.running; } public function fireCurrentTimer():void { if(_currentTimer) { // FIXME _currentTimer.delay = 0.000001; /* _currentTimer.dispatchEvent(new TimerEvent(TimerEvent.TIMER)); _currentTimer.stop(); _currentTimer = null; */ } } public function setCurrentTimerDelay(d:Number):void { if(d == 0) { fireCurrentTimer(); } else { _currentTimer.delay = d; } } } } class IndexInfo { public var btdiff:uint; public var seekpos:uint; public function IndexInfo(tdiff:uint, pos:uint) { btdiff = tdiff; seekpos = pos; } }