└── src ├── leelib └── util │ └── flvEncoder │ ├── ByteArrayFlvEncoder.as │ ├── ByteableByteArray.as │ ├── ByteableFileStream.as │ ├── FileStreamFlvEncoder.as │ ├── FlvEncoder.as │ ├── IByteable.as │ ├── IVideoPayload.as │ ├── MicRecorderUtil.as │ ├── README.TXT │ ├── VideoPayloadMaker.as │ ├── VideoPayloadMakerAlchemy.as │ └── alchemy │ ├── flvEncodeHelper.c │ └── flvEncodeHelper.swc └── leelibExamples └── flvEncoder ├── basic ├── StaticAudioExampleApp.as └── assets │ └── hello.mp3 └── webcam ├── WebcamApp.as └── uiEtc ├── CheckBox.as ├── MinimalFlvPlayback.as ├── RecordButton.as └── States.as /src/leelib/util/flvEncoder/ByteArrayFlvEncoder.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.utils.ByteArray; 4 | 5 | /** 6 | * Encodes FLV's into a ByteArray 7 | */ 8 | public class ByteArrayFlvEncoder extends FlvEncoder 9 | { 10 | public function ByteArrayFlvEncoder($frameRate:Number) 11 | { 12 | super($frameRate); 13 | } 14 | 15 | public function get byteArray():ByteArray 16 | { 17 | return _bytes as ByteArray; 18 | } 19 | 20 | public override function kill():void 21 | { 22 | super.kill(); 23 | byteArray.length = 0; 24 | } 25 | 26 | protected override function makeBytes():void 27 | { 28 | _bytes = new ByteableByteArray(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/ByteableByteArray.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.utils.ByteArray; 4 | 5 | public class ByteableByteArray extends ByteArray implements IByteable 6 | { 7 | public function ByteableByteArray() 8 | { 9 | super(); 10 | } 11 | 12 | public function get canPosition():Boolean 13 | { 14 | return true; 15 | } 16 | 17 | public function get pos():Number 18 | { 19 | return this.position; 20 | } 21 | public function set pos($pos:Number):void 22 | { 23 | this.position = uint($pos); 24 | } 25 | 26 | public function get len():Number 27 | { 28 | return this.length; 29 | } 30 | public function set len($len:Number):void 31 | { 32 | this.length = uint($len); 33 | } 34 | 35 | public function kill():void 36 | { 37 | this.length = 0; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/ByteableFileStream.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.errors.IllegalOperationError; 4 | import flash.filesystem.File; 5 | import flash.filesystem.FileStream; 6 | import flash.utils.ByteArray; 7 | 8 | 9 | public class ByteableFileStream extends FileStream implements IByteable 10 | { 11 | private var _file:File; 12 | 13 | 14 | public function ByteableFileStream($file:File) 15 | { 16 | _file = $file; 17 | super(); 18 | } 19 | 20 | public function get pos():Number 21 | { 22 | return this.position; 23 | } 24 | public function set pos($pos:Number):void 25 | { 26 | this.position = uint($pos); 27 | } 28 | 29 | public function get len():Number 30 | { 31 | return _file.size; 32 | } 33 | 34 | public function kill():void 35 | { 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/FileStreamFlvEncoder.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.filesystem.File; 4 | import flash.filesystem.FileStream; 5 | import flash.utils.ByteArray; 6 | 7 | /** 8 | * Encodes FLV's into a FileStream 9 | */ 10 | public class FileStreamFlvEncoder extends FlvEncoder 11 | { 12 | private var _file:File; 13 | private var _fileStream:ByteableFileStream; 14 | 15 | 16 | public function FileStreamFlvEncoder($file:File, $frameRate:Number) 17 | { 18 | _file = $file; 19 | super($frameRate); 20 | } 21 | 22 | public function get fileStream():FileStream 23 | { 24 | return _bytes as FileStream; 25 | } 26 | 27 | public function get file():File 28 | { 29 | return _file; 30 | } 31 | 32 | public override function kill():void 33 | { 34 | super.kill(); 35 | } 36 | 37 | protected override function makeBytes():void 38 | { 39 | _bytes = new ByteableFileStream(_file); 40 | _fileStream = _bytes as ByteableFileStream; 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/FlvEncoder.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.display.BitmapData; 4 | import flash.errors.IllegalOperationError; 5 | import flash.geom.Rectangle; 6 | import flash.utils.ByteArray; 7 | import flash.utils.Endian; 8 | import flash.utils.IDataOutput; 9 | import flash.utils.getQualifiedClassName; 10 | import flash.utils.getQualifiedSuperclassName; 11 | 12 | /** 13 | * Don't instantiate this class directly. 14 | * Use ByteArrayFlvEncoder or FileStreamFlvEncoder instead. 15 | */ 16 | public class FlvEncoder 17 | { 18 | public static const SAMPLERATE_11KHZ:uint = 11025; 19 | public static const SAMPLERATE_22KHZ:uint = 22050; 20 | public static const SAMPLERATE_44KHZ:uint = 44100; 21 | 22 | public static const BLOCK_WIDTH:int = 32; 23 | public static const BLOCK_HEIGHT:int = 32; 24 | 25 | protected var _frameRate:Number; 26 | protected var _bytes:IByteable; 27 | 28 | private var _duration:Number; 29 | private var _durationPos:int; 30 | 31 | private var _hasVideo:Boolean; 32 | private var _frameWidth:int; 33 | private var _frameHeight:int; 34 | 35 | private var _hasAudio:Boolean; 36 | private var _sampleRate:uint; 37 | private var _is16Bit:Boolean; 38 | private var _isStereo:Boolean; 39 | private var _isAudioInputFloats:Boolean; 40 | 41 | private var _videoPayloadMaker:IVideoPayload; 42 | 43 | private var _soundPropertiesByte:uint; 44 | private var _audioFrameSize:uint; 45 | 46 | private var _lastTagSize:uint = 0; 47 | private var _frameNum:int = 0; 48 | 49 | private var _isStarted:Boolean; 50 | 51 | 52 | /** 53 | * @param $framesPerSecond Dictates the framerate of FLV playback. 54 | */ 55 | public function FlvEncoder($frameRate:Number) 56 | { 57 | var s:String = getQualifiedClassName(this); 58 | s = s.substr(s.indexOf("::")+2); 59 | if (s == "FlvEncoder") throw new IllegalOperationError("FlvEncoder must be instantiated thru a subclass (eg, ByteArrayFlvEncoder or FileStreamFlvEncoder)"); 60 | 61 | _frameRate = $frameRate; 62 | 63 | makeBytes(); 64 | } 65 | 66 | protected function makeBytes():void 67 | { 68 | // 69 | // _bytes must be instantiated here 70 | // 71 | } 72 | 73 | /** 74 | * Defines the video dimensions to be used in the FLV. 75 | * setVideoProperties() must be called before calling "start()" 76 | * 77 | * @param $width Width of each bitmapData to be supplied in addFrame() 78 | * @param $height Width of each bitmapData to be supplied in addFrame() 79 | * 80 | * @param $customVideoPayloadMakerClass 81 | * A custom video encoder class can be specified here. 82 | * (For example, one that is Alchemy or Pixelbender-based). 83 | */ 84 | public function setVideoProperties($width:int, $height:int, $customVideoPayloadMakerClass:Class=null):void 85 | { 86 | if (_isStarted) { 87 | throw new Error("setVideoProperties() must be called before begin()"); 88 | } 89 | 90 | if (! $customVideoPayloadMakerClass) { 91 | _videoPayloadMaker = new VideoPayloadMaker(); 92 | } 93 | else { 94 | _videoPayloadMaker = new $customVideoPayloadMakerClass(); 95 | 96 | if (! _videoPayloadMaker || ! (_videoPayloadMaker is IVideoPayload)) { 97 | throw new Error("$customVideoPayloadMakerClass is not of type IVideoPayload"); 98 | } 99 | } 100 | _videoPayloadMaker.init($width, $height); 101 | 102 | _frameWidth = $width; 103 | _frameHeight = $height; 104 | _hasVideo = true; 105 | } 106 | 107 | /** 108 | * Defines the audio properties to be used in the FLV. 109 | * setAudioProperties() must be called before calling "start()" 110 | * 111 | * @param $sampleRate Should be either SAMPLERATE_11KHZ, SAMPLERATE_22KHZ, or SAMPLERATE_44KHZ 112 | * @param $is16Bit 16-bit audio will be expected if true, 8-bit if false 113 | * Default is true, matching data format coming from Microphone 114 | * @param $isStereo Two channel of audio will be expected if true, one (mono) if false 115 | * Default is false, matching data format coming from Microphone 116 | * @param $dataWillBeInFloats If set to true, audio data supplied to "addFrame()" will be assumed to be 117 | * in floating point format and will be automatically converted to 118 | * unsigned shortints for the FLV. (PCM audio coming from either WAV files or 119 | * from webcam microphone input is in floating point format.) 120 | */ 121 | public function setAudioProperties($sampleRate:uint=0, $is16Bit:Boolean=true, $isStereo:Boolean=false, $dataWillBeInFloats:Boolean=true):void 122 | { 123 | if (_isStarted) { 124 | throw new Error("setAudioProperties() must be called before begin()"); 125 | } 126 | if ($sampleRate != SAMPLERATE_44KHZ && $sampleRate != SAMPLERATE_22KHZ && $sampleRate != SAMPLERATE_11KHZ) { 127 | throw new Error("Invalid samplerate value. Use supplied constants (eg, SAMPLERATE_11KHZ)"); 128 | } 129 | 130 | _sampleRate = $sampleRate; 131 | _is16Bit = $is16Bit; 132 | _isStereo = $isStereo; 133 | _isAudioInputFloats = $dataWillBeInFloats; 134 | 135 | var n:Number = _sampleRate * (_isStereo ? 2 : 1) * (_is16Bit ? 2 : 1); 136 | n = n / _frameRate; 137 | if (_isAudioInputFloats) n *= 2; 138 | _audioFrameSize = int(n); 139 | 140 | _soundPropertiesByte = makeSoundPropertiesByte(); 141 | 142 | _hasAudio = true; 143 | } 144 | 145 | /** 146 | * Must be called after setVideoProperties and/or setAudioProperties 147 | * and before addFrame() gets called. 148 | * 149 | * If setAudioProperties() was not called, the FLV is assumed to be video-only. 150 | * If setVideoProperties() was not called, the FLV is assumed to be audio-only. 151 | */ 152 | public function start():void 153 | { 154 | if (_isStarted) throw new Error("begin() has already been called"); 155 | if (_hasVideo==false && _hasAudio==false) throw new Error("setVideoProperties() and/or setAudioProperties() must be called first"); 156 | 157 | // create header 158 | var ba:ByteArray = new ByteArray(); 159 | ba.writeBytes( makeHeader() ); 160 | 161 | // create metadata tag 162 | ba.writeUnsignedInt( _lastTagSize ); 163 | ba.writeBytes( makeMetaDataTag() ); 164 | 165 | // get and save position of metadata's duration float 166 | var tmp:ByteArray = new ByteArray(); 167 | tmp.writeUTFBytes("duration"); 168 | _durationPos = byteArrayIndexOf(ba, tmp) + tmp.length + 1; 169 | 170 | _bytes.writeBytes(ba); 171 | 172 | } 173 | 174 | /** 175 | * @param $bitmapData Dimensions should match those supplied in setVideoProperties. 176 | * If creating an audio-only FLV, set to null. 177 | * 178 | * @param $pcmAudio Audio properties (bits per sample, channels per sample, and sample-rate) 179 | * should match those supplied in setAudioProperties. 180 | * If creating a video-only FLV, set to null. 181 | */ 182 | public function addFrame($bitmapData:BitmapData, $uncompressedAudio:ByteArray):void 183 | { 184 | if (! _bytes) throw new Error("start() must be called first"); 185 | if (! _hasVideo && $bitmapData) throw new Error("Expecting null for argument 1 because video properties were not defined via setVideoProperties()"); 186 | if (! _hasAudio && $uncompressedAudio) throw new Error("Expecting null for argument 2 because audio properties were not defined via setAudioProperties()"); 187 | if (_hasVideo && ! $bitmapData) throw new Error("Expecting value for argument 1"); 188 | if (_hasAudio && ! $uncompressedAudio) throw new Error("Expecting value for argument 2"); 189 | 190 | if ($bitmapData) 191 | { 192 | _bytes.writeUnsignedInt(_lastTagSize); 193 | writeVideoTagTo(_bytes, $bitmapData); 194 | } 195 | 196 | if ($uncompressedAudio) 197 | { 198 | _bytes.writeUnsignedInt(_lastTagSize); 199 | 200 | // Note how, if _isAudioInputFloats is true (which is the default), 201 | // the incoming audio data is assumed to be in normalized floats 202 | // (4 bytes per float value) and converted to signed shortint's, 203 | // which are 2 bytes per value. Don't let this be a source of confusion... 204 | 205 | var b:ByteArray = _isAudioInputFloats ? floatsToSignedShorts($uncompressedAudio) : $uncompressedAudio; 206 | 207 | writeAudioTagTo(_bytes, b); 208 | } 209 | 210 | _frameNum++; 211 | } 212 | 213 | public function updateDurationMetadata():void 214 | { 215 | _bytes.pos = _durationPos; 216 | _bytes.writeDouble( _frameNum / _frameRate ); 217 | _bytes.pos = _bytes.len; // (restore) 218 | } 219 | 220 | 221 | protected function get bytes():IByteable 222 | { 223 | return _bytes; 224 | } 225 | 226 | public function get frameRate():Number 227 | { 228 | return _frameRate; 229 | } 230 | 231 | /** 232 | * Prepare instance for garbage collection. 233 | */ 234 | public function kill():void 235 | { 236 | _videoPayloadMaker.kill(); 237 | } 238 | 239 | /** 240 | * Convenience property returning the expected size in bytes of the 241 | * audio data that should be supplied in the addFrame() method. 242 | */ 243 | public function get audioFrameSize():uint 244 | { 245 | return _audioFrameSize; 246 | } 247 | 248 | /** 249 | * Convenience method to convert Sound.extract data or SampleDataEvent data 250 | * into linear PCM format used for uncompressed FLV audio. 251 | * I.e., converts normalized floats to signed shortints. 252 | */ 253 | public static function floatsToSignedShorts($ba:ByteArray):ByteArray 254 | { 255 | var out:ByteArray = new ByteArray(); 256 | out.endian = Endian.LITTLE_ENDIAN; 257 | 258 | $ba.position = 0; 259 | var num:int = $ba.length / 4; 260 | 261 | for (var i:int = 0; i < num; i++) 262 | { 263 | var n:Number = $ba.readFloat(); 264 | var val:int = n * 32768; 265 | out.writeShort(val); 266 | } 267 | 268 | return out; 269 | } 270 | 271 | // 272 | 273 | private function makeHeader():ByteArray 274 | { 275 | var baHeader:ByteArray = new ByteArray(); 276 | 277 | baHeader.writeByte(0x46) // 'F' 278 | baHeader.writeByte(0x4C) // 'L' 279 | baHeader.writeByte(0x56) // 'V' 280 | baHeader.writeByte(0x01) // Version 1 281 | 282 | // streams: video and/or audio 283 | var u:uint = 0; 284 | if (_hasVideo) u += 1; 285 | if (_hasAudio) u += 4; 286 | baHeader.writeByte(u); 287 | 288 | baHeader.writeUnsignedInt(0x09) // header length 289 | 290 | return baHeader; 291 | } 292 | 293 | private function makeMetaDataTag():ByteArray 294 | { 295 | var baTag:ByteArray = new ByteArray(); 296 | var baMetaData:ByteArray = makeMetaData(); 297 | 298 | // tag 'header' 299 | baTag.writeByte( 18 ); // tagType = script data 300 | writeUI24(baTag, baMetaData.length); // data size 301 | writeUI24(baTag, 0); // timestamp should be 0 for onMetaData tag 302 | baTag.writeByte(0); // timestamp extended 303 | writeUI24(baTag, 0); // streamID always 0 304 | 305 | // payload 306 | baTag.writeBytes( baMetaData ); 307 | 308 | _lastTagSize = baTag.length; 309 | return baTag; 310 | } 311 | 312 | private function makeMetaData():ByteArray 313 | { 314 | // onMetaData info goes in a ScriptDataObject of data type 'ECMA Array' 315 | 316 | var b:ByteArray = new ByteArray(); 317 | 318 | // ObjectNameType (always 2) 319 | b.writeByte(2); 320 | 321 | // ObjectName (type SCRIPTDATASTRING): 322 | writeUI16(b, "onMetaData".length); // StringLength 323 | b.writeUTFBytes( "onMetaData" ); // StringData 324 | 325 | // ObjectData (type SCRIPTDATAVALUE): 326 | 327 | b.writeByte(8); // Type (ECMA array = 8) 328 | b.writeUnsignedInt(7) // // Elements in array 329 | 330 | // SCRIPTDATAVARIABLES... 331 | 332 | writeUI16(b, "duration".length); 333 | b.writeUTFBytes("duration"); 334 | b.writeByte(0); 335 | b.writeDouble(0.0); // * this value will get updated dynamically with addFrame() 336 | 337 | writeUI16(b, "width".length); 338 | b.writeUTFBytes("width"); 339 | b.writeByte(0); 340 | b.writeDouble(_frameWidth); 341 | 342 | writeUI16(b, "height".length); 343 | b.writeUTFBytes("height"); 344 | b.writeByte(0); 345 | b.writeDouble(_frameHeight); 346 | 347 | writeUI16(b, "framerate".length); 348 | b.writeUTFBytes("framerate"); 349 | b.writeByte(0); 350 | b.writeDouble(_frameRate); 351 | 352 | writeUI16(b, "videocodecid".length); 353 | b.writeUTFBytes("videocodecid"); 354 | b.writeByte(0); 355 | b.writeDouble(3); // 'Screen Video' = 3 356 | 357 | writeUI16(b, "canSeekToEnd".length); 358 | b.writeUTFBytes("canSeekToEnd"); 359 | b.writeByte(1); 360 | b.writeByte(int(true)); 361 | 362 | var mdc:String = "FlvEncoder v0.9 Lee Felarca"; 363 | writeUI16(b, "metadatacreator".length); 364 | b.writeUTFBytes("metadatacreator"); 365 | b.writeByte(2); 366 | writeUI16(b, mdc.length); 367 | b.writeUTFBytes(mdc); 368 | 369 | // VariableEndMarker1 (type UI24 - always 9) 370 | writeUI24(b, 9); 371 | 372 | return b; 373 | } 374 | 375 | private function writeVideoTagTo($bytes:IByteable, $bitmapData:BitmapData):void 376 | { 377 | var pos:int = $bytes.pos; 378 | 379 | var ba:ByteArray = _videoPayloadMaker.make($bitmapData); 380 | 381 | var timeStamp:uint = uint(1000/_frameRate * _frameNum); 382 | 383 | // tag 'header' 384 | $bytes.writeByte( 0x09 ); // tagType = video 385 | writeUI24($bytes, ba.length); // data size 386 | writeUI24($bytes, timeStamp); // timestamp in ms 387 | $bytes.writeByte(0); // timestamp extended, no need 388 | writeUI24($bytes, 0); // streamID always 0 389 | 390 | // payload 391 | $bytes.writeBytes( ba ); 392 | 393 | _lastTagSize = $bytes.pos - pos; 394 | 395 | ba.length = 0; 396 | ba = null; 397 | } 398 | 399 | private function writeAudioTagTo($bytes:IByteable, $pcmData:ByteArray):void 400 | { 401 | var pos:int = $bytes.pos; 402 | 403 | $bytes.writeByte( 0x08 ); // TagType - 8 = audio 404 | writeUI24($bytes, $pcmData.length+1); // DataSize ("+1" for header) 405 | var timeStamp:uint = uint(1000/_frameRate * _frameNum); 406 | writeUI24($bytes, timeStamp); // Timestamp (ms) 407 | $bytes.writeByte(0); // TimestampExtended - not using 408 | writeUI24($bytes, 0); // StreamID - always 0 409 | 410 | // AUDIODATA 411 | $bytes.writeByte(_soundPropertiesByte); // header 412 | $bytes.writeBytes($pcmData); // real sound data 413 | 414 | _lastTagSize = $bytes.pos - pos; 415 | } 416 | 417 | private function makeSoundPropertiesByte():uint 418 | { 419 | var u:uint, val:int; 420 | 421 | // soundformat [4 bits] - only supporting linear PCM little endian == 3 422 | u = (3 << 4); 423 | 424 | // soundrate [2 bits] 425 | switch(_sampleRate) { 426 | case SAMPLERATE_11KHZ: val = 1; break; 427 | case SAMPLERATE_22KHZ: val = 2; break; 428 | case SAMPLERATE_44KHZ: val = 3; break; 429 | } 430 | u += (val << 2); 431 | 432 | // soundsize [1 bit] - 0 = 8bit; 1 = 16bit 433 | val = _is16Bit ? 1 : 0; 434 | u += (val << 1); 435 | 436 | // soundtype [1 bit] - 0 = mono; 1 = stereo 437 | val = _isStereo ? 1 : 0; 438 | u += (val << 0); 439 | 440 | // trace('FlvEncoder.makeSoundPropertiesByte():', u.toString(2)); 441 | 442 | return u; 443 | } 444 | 445 | public static function byteArrayIndexOf($ba:ByteArray, $searchTerm:ByteArray):int 446 | { 447 | var origPosBa:int = $ba.position; 448 | var origPosSearchTerm:int = $searchTerm.position; 449 | 450 | var end:int = $ba.length - $searchTerm.length 451 | for (var i:int = 0; i <= end; i++) 452 | { 453 | if (byteArrayEqualsAt($ba, $searchTerm, i)) 454 | { 455 | $ba.position = origPosBa; 456 | $searchTerm.position = origPosSearchTerm; 457 | return i; 458 | } 459 | } 460 | 461 | $ba.position = origPosBa; 462 | $searchTerm.position = origPosSearchTerm; 463 | return -1; 464 | } 465 | 466 | public static function byteArrayEqualsAt($ba:ByteArray, $searchTerm:ByteArray, $position:int):Boolean 467 | { 468 | // NB, function will modify byteArrays' cursors 469 | 470 | if ($position + $searchTerm.length > $ba.length) return false; 471 | 472 | $ba.position = $position; 473 | $searchTerm.position = 0; 474 | 475 | for (var i:int = 0; i < $searchTerm.length; i++) 476 | { 477 | var valBa:int = $ba.readByte(); 478 | var valSearch:int = $searchTerm.readByte(); 479 | 480 | if (valBa != valSearch) return false; 481 | } 482 | 483 | return true; 484 | } 485 | 486 | public static function writeUI24(stream:*, p:uint):void 487 | { 488 | var byte1:int = p >> 16; 489 | var byte2:int = p >> 8 & 0xff; 490 | var byte3:int = p & 0xff; 491 | stream.writeByte(byte1); 492 | stream.writeByte(byte2); 493 | stream.writeByte(byte3); 494 | } 495 | 496 | public static function writeUI16(stream:*, p:uint):void 497 | { 498 | stream.writeByte( p >> 8 ) 499 | stream.writeByte( p & 0xff ); 500 | } 501 | 502 | public static function writeUI4_12(stream:*, p1:uint, p2:uint):void 503 | { 504 | // writes a 4-bit value followed by a 12-bit value in a total of 2 bytes 505 | 506 | var byte1a:int = p1 << 4; 507 | var byte1b:int = p2 >> 8; 508 | var byte1:int = byte1a + byte1b; 509 | var byte2:int = p2 & 0xff; 510 | 511 | stream.writeByte(byte1); 512 | stream.writeByte(byte2); 513 | } 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/IByteable.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.utils.IDataInput; 4 | import flash.utils.IDataOutput; 5 | 6 | /** 7 | * IBytes allows FlvEncoder to do byte operations on either a ByteArray or a FileStream instance, 8 | * without explicitly typing to either. 9 | * 10 | * But must be used with an instance of "ByteArrayWrapper" or "FileStreamWrapper" rather than 11 | * ByteArray or FileStream directly. 12 | * 13 | * "position" has a different signature in ByteArray versus FileStream (uint versus Number), 14 | * so it gets wrapped with the getter/setter "pos" 15 | * 16 | * "length" also needs to values > 2^32 so same treatment applies 17 | */ 18 | public interface IByteable extends IDataInput, IDataOutput 19 | { 20 | function get pos():Number; 21 | function set pos($n:Number):void; 22 | 23 | function get len():Number; 24 | 25 | function kill():void; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/IVideoPayload.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.display.BitmapData; 4 | import flash.utils.ByteArray; 5 | 6 | public interface IVideoPayload 7 | { 8 | function init($width:int, $height:int):void 9 | function make($bitmapData:BitmapData):ByteArray; 10 | function kill():void 11 | } 12 | } -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/MicRecorderUtil.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.events.SampleDataEvent; 6 | import flash.events.StatusEvent; 7 | import flash.media.Microphone; 8 | import flash.utils.ByteArray; 9 | import flash.utils.getTimer; 10 | 11 | 12 | /** 13 | * Mostly duplicated from ByteArray's MicRecorder class 14 | * http://www.bytearray.org/?p=1858 15 | * 16 | * It couldn't be extended because is final. 17 | */ 18 | public class MicRecorderUtil extends EventDispatcher 19 | { 20 | private var _startTime:uint; 21 | private var _microphone:Microphone; 22 | private var _byteArray:ByteArray = new ByteArray(); 23 | 24 | private var _completeEvent:Event = new Event ( Event.COMPLETE ); 25 | 26 | 27 | public function MicRecorderUtil(microphone:Microphone) 28 | { 29 | _microphone = microphone; 30 | } 31 | 32 | public function record():void 33 | { 34 | _startTime = getTimer(); 35 | 36 | _byteArray.length = 0; 37 | 38 | _microphone.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData); 39 | _microphone.addEventListener(StatusEvent.STATUS, onStatus); // (may never fire) 40 | } 41 | 42 | public function stop():void 43 | { 44 | _microphone.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData); 45 | _microphone.removeEventListener(StatusEvent.STATUS, onStatus); 46 | 47 | _byteArray.position = 0; 48 | } 49 | 50 | public function get microphone():Microphone 51 | { 52 | return _microphone; 53 | } 54 | 55 | public function get byteArray():ByteArray 56 | { 57 | return _byteArray; 58 | } 59 | 60 | /** 61 | * Effectly removes the first $pos bytes from _byteArray. 62 | * NB, creates a new instance of ByteArray; cursor ends up at the end. 63 | */ 64 | public function shift($pos:uint):void 65 | { 66 | var ba:ByteArray = new ByteArray(); 67 | ba.writeBytes(_byteArray, $pos, _byteArray.length - $pos); 68 | _byteArray = ba; 69 | } 70 | 71 | // 72 | 73 | private function onStatus($e:StatusEvent):void 74 | { 75 | trace('Mic - onStatus', $e.code) 76 | 77 | _startTime = getTimer(); 78 | } 79 | 80 | private function onSampleData($e:SampleDataEvent):void 81 | { 82 | // ADD THIS LATER POSSIBLY 83 | // _recordingEvent.time = getTimer() - _startTime; 84 | 85 | while($e.data.bytesAvailable > 0) { 86 | _byteArray.writeFloat($e.data.readFloat()); 87 | } 88 | 89 | // ADD THIS LATER POSSIBLY 90 | // private var _recordingEvent:RecordingEvent = new RecordingEvent( RecordingEvent.RECORDING, 0 ); 91 | // this.dispatchEvent( _recordingEvent ); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/README.TXT: -------------------------------------------------------------------------------- 1 | FlvEncoder (leelib.util.flvEncoder.*) 2 | Lee Felarca 3 | http://www.zeropointnine.com/blog 4 | 3-29-2011 5 | Package version: v0.9b 6 | 7 | Creates uncompressed FLV's with video and audio without server-side dependency. 8 | 9 | See http://www.zeropointnine.com/blog/updated-flv-encoder-alchemy for more info. 10 | 11 | FLV spec is described here: http://www.adobe.com/devnet/f4v.html 12 | 13 | 14 | Example usage: 15 | 16 | [1] ByteArrayFlvEncoder: 17 | 18 | var baFlvEncoder:ByteArrayFlvEncoder = new ByteArrayFlvEncoder(myFrameRate); 19 | 20 | baFlvEncoder.setVideoProperties(myWidth, myHeight, VideoPayloadMakerAlchemy); 21 | // (Omit the 3rd argument to NOT use the Alchemy encoding routine) 22 | baFlvEncoder.setAudioProperties(BaseFlvEncoder.SAMPLERATE_44KHZ, true, false, true); 23 | 24 | baFlvEncoder.start(); 25 | 26 | baFlvEncoder.addFrame(myBitmapData, myAudioByteArray); 27 | baFlvEncoder.addFrame(myBitmapData, myAudioByteArray); // etc. 28 | 29 | baFlvEncoder.updateDurationMetadata(); 30 | 31 | saveOutMyFileUsingFileReference( baFlvEncoder.byteArray ); 32 | 33 | baFlvEncoder.kill(); // for garbage collection 34 | 35 | [2] FileStreamFlvEncoder (using Adobe AIR) 36 | 37 | var myFile:File = File.documentsDirectory.resolvePath("video.flv"); 38 | var fsFlvEncoder:FileStreamFlvEncoder = new FileStreamFlvEncoder(myFile, myFrameRate); 39 | fsFlvEncoder.fileStream.openAsync(myFile, FileMode.UPDATE); 40 | 41 | fsFlvEncoder.setVideoProperties(myWidth, myHeight, VideoPayloadMakerAlchemy); 42 | // (Omit the 3rd argument to NOT use the Alchemy encoding routine) 43 | fsFlvEncoder.setAudioProperties(BaseFlvEncoder.SAMPLERATE_44KHZ, true, false, true); 44 | 45 | fsFlvEncoder.start(); 46 | 47 | fsFlvEncoder.addFrame(myBitmapData, myAudioByteArray); 48 | fsFlvEncoder.addFrame(myBitmapData, myAudioByteArray); // etc. 49 | 50 | fsFlvEncoder.updateDurationMetadata(); 51 | 52 | fsFlvEncoder.fileStream.close(); 53 | 54 | fsFlvEncoder.kill(); 55 | 56 | *** To use the Alchemy version, add a SWC folder reference to leelib/util/flvEncoder/alchemy 57 | 58 | 59 | Source code licensed under a Creative Commons Attribution 3.0 License. 60 | http://creativecommons.org/licenses/by/3.0/ 61 | Some Rights Reserved. 62 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/VideoPayloadMaker.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import flash.display.BitmapData; 4 | import flash.system.System; 5 | import flash.utils.ByteArray; 6 | import flash.utils.Endian; 7 | 8 | /** 9 | * AS-3 only algorithm. 10 | * No SWC dependencies, no Flash 10 requirement 11 | */ 12 | public class VideoPayloadMaker implements IVideoPayload 13 | { 14 | public function make($bitmapData:BitmapData):ByteArray 15 | { 16 | var w:int = $bitmapData.width; 17 | var h:int = $bitmapData.height; 18 | 19 | var ba:ByteArray = new ByteArray(); 20 | 21 | // VIDEODATA 'header' - frametype (1) + codecid (3) 22 | ba.writeByte(0x13); 23 | 24 | // SCREENVIDEOPACKET 'header' 25 | FlvEncoder.writeUI4_12(ba, int(FlvEncoder.BLOCK_WIDTH/16) - 1, w); // blockwidth/16-1 (4bits) + imagewidth (12bits) 26 | FlvEncoder.writeUI4_12(ba, int(FlvEncoder.BLOCK_HEIGHT/16) - 1, h); // blockheight/16-1 (4bits) + imageheight (12bits) 27 | 28 | // IMAGEBLOCKS 29 | 30 | var rowMax:int = int(h/FlvEncoder.BLOCK_HEIGHT); 31 | var rowRemainder:int = h % FlvEncoder.BLOCK_HEIGHT; 32 | if (rowRemainder > 0) rowMax += 1; 33 | 34 | var colMax:int = int(w/FlvEncoder.BLOCK_WIDTH); 35 | var colRemainder:int = w % FlvEncoder.BLOCK_WIDTH; 36 | if (colRemainder > 0) colMax += 1; 37 | 38 | var block:ByteArray = new ByteArray(); 39 | block.endian = Endian.LITTLE_ENDIAN; 40 | 41 | for (var row:int = 0; row < rowMax; row++) 42 | { 43 | for (var col:int = 0; col < colMax; col++) 44 | { 45 | var xStart:uint = col * FlvEncoder.BLOCK_WIDTH; 46 | var xLimit:int = (colRemainder > 0 && col + 1 == colMax) ? colRemainder : FlvEncoder.BLOCK_WIDTH; 47 | var xEnd:int = xStart + xLimit; 48 | 49 | var yStart:uint = h - (row * FlvEncoder.BLOCK_HEIGHT); // * goes from bottom to top 50 | var yLimit:int = (rowRemainder > 0 && row + 1 == rowMax) ? rowRemainder : FlvEncoder.BLOCK_HEIGHT; 51 | var yEnd:int = yStart - yLimit; 52 | 53 | // re-use ByteArray 54 | block.length = 0; 55 | 56 | for (var y:int = yStart-1; y >= yEnd; y--) // (flv's store image data from bottom to top) 57 | { 58 | for (var x:int = xStart; x < xEnd; x++) 59 | { 60 | var p:uint = $bitmapData.getPixel(x, y); 61 | block.writeByte( p & 0xff ); 62 | block.writeShort(p >> 8); 63 | // ... this is the equivalent of writing the B, G, and R bytes in sequence 64 | } 65 | } 66 | 67 | block.compress(); 68 | 69 | FlvEncoder.writeUI16(ba, block.length); // write block length (UI16) 70 | ba.writeBytes( block ); // write block 71 | } 72 | } 73 | 74 | block.length = 0; 75 | block = null; 76 | 77 | return ba; 78 | } 79 | 80 | public function init($width:int, $height:int):void 81 | { 82 | // (no particular need in AS3 version) 83 | } 84 | 85 | public function kill():void 86 | { 87 | // (no particular need in AS3 version) 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/VideoPayloadMakerAlchemy.as: -------------------------------------------------------------------------------- 1 | package leelib.util.flvEncoder 2 | { 3 | import cmodule.flvEncodeHelper.CLibInit; 4 | 5 | import flash.display.BitmapData; 6 | import flash.geom.Rectangle; 7 | import flash.utils.ByteArray; 8 | import flash.utils.Endian; 9 | 10 | /** 11 | * Technique for Alchemy memory allocation/access comes from Bernard Visscher: 12 | * http://blog.debit.nl/2009/03/using-bytearrays-in-actionscript-and-alchemy/ 13 | */ 14 | public class VideoPayloadMakerAlchemy implements IVideoPayload 15 | { 16 | private static var _alchemyLoader:CLibInit; 17 | 18 | private var _width:int; 19 | private var _height:int; 20 | 21 | private var _helper:Object; 22 | private var _alchemyBufferPos:uint; 23 | private var _baBuffer:ByteArray; 24 | 25 | public function VideoPayloadMakerAlchemy() 26 | { 27 | 28 | if (!_alchemyLoader) _alchemyLoader = new CLibInit(); 29 | 30 | _helper = _alchemyLoader.init(); 31 | if (! _helper) throw new Error("Bad Alchemy build?"); 32 | } 33 | 34 | public function init($width:int, $height:int):void 35 | { 36 | _width = $width; 37 | _height = $height; 38 | 39 | _alchemyBufferPos = _helper.initBuffer($width, $height); 40 | var ns:Namespace = new Namespace("cmodule.flvEncodeHelper"); 41 | _baBuffer = (ns::gstate).ds; 42 | } 43 | 44 | public function make($bitmapData:BitmapData):ByteArray 45 | { 46 | var w:int = $bitmapData.width; 47 | var h:int = $bitmapData.height; 48 | 49 | var baResult:ByteArray = new ByteArray(); 50 | 51 | // VIDEODATA 'header' - frametype (1) + codecid (3) 52 | baResult.writeByte(0x13); 53 | 54 | // SCREENVIDEOPACKET 'header' 55 | FlvEncoder.writeUI4_12(baResult, int(FlvEncoder.BLOCK_WIDTH/16) - 1, w); // blockwidth/16-1 (4bits) + imagewidth (12bits) 56 | FlvEncoder.writeUI4_12(baResult, int(FlvEncoder.BLOCK_HEIGHT/16) - 1, h); // blockheight/16-1 (4bits) + imageheight (12bits) 57 | 58 | // IMAGEBLOCKS 59 | 60 | // Make byteArray of bitmapData 61 | var baBitmap:ByteArray = $bitmapData.getPixels(new Rectangle(0,0,$bitmapData.width,$bitmapData.height)); 62 | 63 | // Write byteArray to alchemy buffer 64 | _baBuffer.position = _alchemyBufferPos; 65 | _baBuffer.writeBytes(baBitmap); 66 | 67 | baBitmap.position = 0; 68 | baBitmap = null; 69 | 70 | // Process data in alchemy 71 | var baImg:ByteArray = new ByteArray(); 72 | var baIdx:ByteArray = new ByteArray(); 73 | 74 | _helper.makeImageBlocks(baImg, baIdx); 75 | 76 | // Parse data and write to byteArray 77 | writeAlchemyDataTo(baResult, baImg, baIdx); 78 | 79 | baImg.position = 0; 80 | baImg = null; 81 | baIdx.position = 0; 82 | baIdx = null; 83 | 84 | return baResult; 85 | } 86 | 87 | public function kill():void 88 | { 89 | _helper.clear(); 90 | } 91 | 92 | /** 93 | * @param $baVideo ByteArray being added to 94 | * @param $baAlchemy The processed but still uncompressed video frame data coming out of the Alchemy routine 95 | * @param $baIndices Indices (unsigned shorts) of byteArray positions of each chunk in the $baAlchemy ByteArray. 96 | */ 97 | private function writeAlchemyDataTo($baVideo:ByteArray, $baAlchemy:ByteArray, $baIndices:ByteArray):void 98 | { 99 | $baAlchemy.position = 0; 100 | var cursor:int = 0; 101 | 102 | $baIndices.endian = Endian.BIG_ENDIAN; 103 | $baIndices.position = 0; 104 | 105 | var block:ByteArray = new ByteArray(); 106 | 107 | while ($baIndices.position < $baIndices.length-1) 108 | { 109 | var numBytes:int = $baIndices.readUnsignedShort(); 110 | block.length = 0; 111 | block.writeBytes($baAlchemy, cursor, numBytes); 112 | block.compress(); 113 | 114 | FlvEncoder.writeUI16($baVideo, block.length); // write block length (UI16) 115 | $baVideo.writeBytes( block ); // write block 116 | 117 | cursor += numBytes; 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/alchemy/flvEncodeHelper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "AS3.h" 5 | 6 | static unsigned char* buffer; 7 | int bufferSize; 8 | 9 | int width; 10 | int height; 11 | 12 | static unsigned char* output; 13 | int outputLength; 14 | 15 | const int BLOCK_WIDTH = 32; 16 | const int BLOCK_HEIGHT = 32; 17 | 18 | static AS3_Val initBuffer(void* self, AS3_Val args) 19 | { 20 | AS3_ArrayValue(args, "IntType, IntType", &width, &height); 21 | 22 | // Allocate buffer of size "bufferSize" 23 | bufferSize = width * height * 4; 24 | buffer = (unsigned char*)malloc(bufferSize*sizeof(char)); 25 | 26 | // Also the helper array used in makeImageBlocks() 27 | outputLength = width * height * 3; 28 | output = (unsigned char*)malloc(outputLength); 29 | 30 | 31 | //return pointer to the location in memory 32 | return AS3_Int((int)buffer); 33 | } 34 | 35 | 36 | // NOT USING THIS 37 | static AS3_Val writeData(void* self, AS3_Val args) 38 | { 39 | char *tempBuffer; 40 | AS3_ArrayValue(args, "StrType", &tempBuffer); 41 | 42 | //copy string to buffer 43 | strcpy((char*)buffer, (char*)tempBuffer); 44 | 45 | return AS3_String((char*)tempBuffer); 46 | } 47 | 48 | static AS3_Val clearBuffer(void* self, AS3_Val args) 49 | { 50 | //free the buffer 51 | free(buffer); 52 | *buffer = 0; 53 | return 0; 54 | } 55 | 56 | /* 57 | Makes all image blocks that make up a frame in one 58 | flattened ByteArray plus an array of offset indicies, which 59 | will then be handed off to Flash for final assembly of the 60 | FLV video frame. 61 | 62 | (Each image block needs to be compressed, which will be done 63 | on the Flash side, taking advantage of Flash Player's 'native' 64 | zlib compress routine) 65 | 66 | (General strategy is to minimize context switching between 67 | Alchemy and Flash) 68 | 69 | Argument 1: ByteArray which will take image data result 70 | 71 | Argument 2: ByteArray of unsigned shorts that are indices of 72 | each of the image blocks 73 | */ 74 | static AS3_Val makeImageBlocks(void* self, AS3_Val args) 75 | { 76 | AS3_Val asOutput = AS3_GetS(args, "0"); 77 | AS3_Val asIndices = AS3_GetS(args, "1"); 78 | 79 | // unsigned char * output = (unsigned char*)malloc(outputLength); 80 | 81 | int idx = 0; 82 | int count = 0; 83 | 84 | int rowMax = (int)(height / BLOCK_HEIGHT); 85 | int rowRemainder = height % BLOCK_HEIGHT; 86 | if (rowRemainder > 0) rowMax += 1; 87 | 88 | int colMax = (int)(width / BLOCK_WIDTH); 89 | int colRemainder = width % BLOCK_WIDTH; 90 | if (colRemainder > 0) colMax += 1; 91 | 92 | int indicesLength = rowMax * colMax; 93 | unsigned char * indices = (unsigned char*)malloc(indicesLength*2); 94 | 95 | int row; 96 | for (row = 0; row < rowMax; row++) 97 | { 98 | int col; 99 | for (col = 0; col < colMax; col++) 100 | { 101 | int xStart = col * BLOCK_WIDTH; 102 | int xLimit = (colRemainder > 0 && col + 1 == colMax) ? colRemainder : BLOCK_WIDTH; 103 | int xEnd = xStart + xLimit; 104 | 105 | int yStart = height - (row * BLOCK_HEIGHT); // * goes from bottom to top 106 | int yLimit = (rowRemainder > 0 && row + 1 == rowMax) ? rowRemainder : BLOCK_HEIGHT; 107 | int yEnd = yStart - yLimit; 108 | 109 | int pos = idx; 110 | 111 | int y; 112 | for (y = yStart-1; y >= yEnd; y--) 113 | { 114 | int off = (y * width * 4) + (xStart * 4) + 1; // "+1" means R byte instead of A byte 115 | 116 | int x; 117 | for (x = xStart; x < xEnd; x++) 118 | { 119 | output[idx++] = buffer[off+2]; 120 | output[idx++] = buffer[off+1]; 121 | output[idx++] = buffer[off ]; 122 | 123 | off += 4; 124 | } 125 | } 126 | 127 | // Creating an unsigned short that won't get garbled across the bridge 128 | // (Not 100% sure it's necessary to do it this way) 129 | int len = idx - pos; 130 | indices[count++] = (unsigned char) (len >> 8); 131 | indices[count++] = (unsigned char) (len & 0xFF); 132 | } 133 | } 134 | 135 | AS3_ByteArray_writeBytes(asOutput, output, outputLength); 136 | AS3_ByteArray_writeBytes(asIndices, indices, indicesLength*2); 137 | 138 | // cleanup 139 | // (Note how we're not clearing buffer, which is just reused; must be clear()'ed at the end of use.) 140 | *indices = 0; 141 | 142 | AS3_Release(asOutput); 143 | AS3_Release(asIndices); 144 | 145 | return AS3_Int(0); 146 | } 147 | 148 | 149 | // Not implemented yet (someday) 150 | static AS3_Val makeImageBlocksAsync(void* self, AS3_Val args) 151 | { 152 | flyield(); 153 | makeImageBlocks(self, args); 154 | } 155 | 156 | static AS3_Val clear(void* self, AS3_Val args) 157 | { 158 | free(output); 159 | *output = 0; 160 | } 161 | 162 | int main() 163 | { 164 | // *** makeImageBlocksAsyncMethod() not real yet 165 | 166 | AS3_Val initBufferMethod = AS3_Function(NULL, initBuffer); 167 | AS3_Val writeDataMethod = AS3_Function(NULL, writeData); 168 | AS3_Val clearBufferMethod = AS3_Function(NULL, clearBuffer); 169 | AS3_Val makeImageBlocksMethod = AS3_Function(NULL, makeImageBlocks); 170 | AS3_Val makeImageBlocksAsyncMethod = AS3_Function(NULL, makeImageBlocksAsync); 171 | AS3_Val clearMethod = AS3_Function(NULL, clear); 172 | 173 | AS3_Val result = AS3_Object("initBuffer:AS3ValType, writeData:AS3ValType, clearBuffer:AS3ValType, makeImageBlocks:AS3ValType, makeImageBlocksAsync:AS3ValType, clear:AS3ValType", initBufferMethod, writeDataMethod, clearBufferMethod, makeImageBlocksMethod, makeImageBlocksAsyncMethod, clearMethod); 174 | 175 | AS3_Release(initBufferMethod); 176 | AS3_Release(writeDataMethod); 177 | AS3_Release(clearBufferMethod); 178 | AS3_Release(makeImageBlocksMethod); 179 | AS3_Release(makeImageBlocksAsyncMethod); 180 | AS3_Release(clearMethod); 181 | 182 | AS3_LibInit(result); 183 | return 0; 184 | } 185 | 186 | // CLEAN UPSKI -------------------------------------------------------------------------------- /src/leelib/util/flvEncoder/alchemy/flvEncodeHelper.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeropointnine/leelib/efa5e0c78c130f21868db4311d2cc46550593661/src/leelib/util/flvEncoder/alchemy/flvEncodeHelper.swc -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/basic/StaticAudioExampleApp.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.basic 2 | { 3 | import flash.display.Bitmap; 4 | import flash.display.BitmapData; 5 | import flash.display.Sprite; 6 | import flash.display.StageAlign; 7 | import flash.display.StageScaleMode; 8 | import flash.events.Event; 9 | import flash.events.MouseEvent; 10 | import flash.media.Sound; 11 | import flash.media.SoundChannel; 12 | import flash.net.FileReference; 13 | import flash.text.TextField; 14 | import flash.text.TextFieldAutoSize; 15 | import flash.text.TextFormat; 16 | import flash.utils.ByteArray; 17 | import flash.utils.getTimer; 18 | 19 | import leelib.util.flvEncoder.ByteArrayFlvEncoder; 20 | import leelib.util.flvEncoder.FlvEncoder; 21 | 22 | 23 | /** 24 | * Example of using ByteArrayFlvEncoder to create an FLV whose source 25 | * audio is static (eg, a music soundtrack). 26 | * 27 | * Resulting video is 8 seconds of a red bouncing ball on a gray-scale perlin 28 | * noise background, with the beginning of the song "Hello" by Capsule. 29 | */ 30 | 31 | [SWF(width="340", height="290", frameRate="30")] 32 | public class StaticAudioExampleApp extends Sprite 33 | { 34 | [Embed(source="./assets/hello.mp3")] 35 | private static const Soundtrack:Class; 36 | 37 | private const OUTPUT_WIDTH:Number = 320; 38 | private const OUTPUT_HEIGHT:Number = 240; 39 | private const FLV_FRAMERATE:int = 30; 40 | 41 | private var _videoOutput:Sprite; 42 | private var _ball:Sprite; 43 | private var _vx:Number = 12; 44 | private var _vy:Number = 6; 45 | 46 | private var _sound:Sound; 47 | private var _soundChannel:SoundChannel; 48 | 49 | private var _bitmaps:Array; 50 | 51 | private var _baFlvEncoder:ByteArrayFlvEncoder; 52 | 53 | 54 | public function StaticAudioExampleApp() 55 | { 56 | init(); 57 | generateFrames(); 58 | save(); 59 | } 60 | 61 | private function init():void 62 | { 63 | _videoOutput = new Sprite(); 64 | 65 | var bmd:BitmapData = new BitmapData(OUTPUT_WIDTH,OUTPUT_HEIGHT,false); 66 | bmd.perlinNoise(OUTPUT_WIDTH/2,OUTPUT_HEIGHT/2, 3, 666, false,false, 7, true); 67 | _videoOutput.addChild(new Bitmap(bmd)); 68 | 69 | this.addChild(_videoOutput); 70 | 71 | _ball = new Sprite(); 72 | _ball.graphics.beginFill(0xff0000); 73 | _ball.graphics.drawCircle(0,0,10); 74 | _ball.graphics.endFill(); 75 | _videoOutput.addChild(_ball); 76 | 77 | _bitmaps = new Array(); 78 | 79 | _sound = new Soundtrack(); 80 | } 81 | 82 | private function generateFrames():void 83 | { 84 | for (var i:int = 0; i < FLV_FRAMERATE*8; i++) 85 | { 86 | updateDisplay(); 87 | 88 | var b:BitmapData = new BitmapData(OUTPUT_WIDTH,OUTPUT_HEIGHT,false,0x0); 89 | b.draw(_videoOutput); 90 | _bitmaps.push(b); 91 | } 92 | } 93 | 94 | private function onEnterFrame(e:*):void 95 | { 96 | updateDisplay(); 97 | 98 | var b:BitmapData = new BitmapData(OUTPUT_WIDTH,OUTPUT_HEIGHT,false,0x0); 99 | b.draw(_videoOutput); 100 | _bitmaps.push(b); 101 | 102 | // 103 | 104 | if (_bitmaps.length / FLV_FRAMERATE >= 5.0) 105 | { 106 | this.removeEventListener(Event.ENTER_FRAME, onEnterFrame); 107 | _sound.close(); 108 | save(); 109 | } 110 | } 111 | 112 | private function updateDisplay():void 113 | { 114 | _ball.x += _vx; 115 | _ball.y += _vy; 116 | 117 | if (_ball.x > 310) { 118 | _ball.x = 310; 119 | _vx = - Math.abs(_vx); 120 | } 121 | if (_ball.x < 10) { 122 | _ball.x = 10; 123 | _vx = + Math.abs(_vx); 124 | } 125 | 126 | if (_ball.y > 230) { 127 | _ball.y = 230; 128 | _vy = - Math.abs(_vy); 129 | } 130 | if (_ball.y < 10) { 131 | _ball.y = 10; 132 | _vy = + Math.abs(_vy); 133 | } 134 | } 135 | 136 | private function save():void 137 | { 138 | // Prepare the audio data 139 | 140 | var baAudio:ByteArray = new ByteArray(); 141 | var seconds:Number = _bitmaps.length / FLV_FRAMERATE; 142 | _sound.extract(baAudio, seconds * 44000 + 1000); 143 | 144 | // Make FlvEncoder object 145 | 146 | _baFlvEncoder = new ByteArrayFlvEncoder(FLV_FRAMERATE); 147 | _baFlvEncoder.setVideoProperties(OUTPUT_WIDTH, OUTPUT_HEIGHT); 148 | _baFlvEncoder.setAudioProperties(FlvEncoder.SAMPLERATE_44KHZ, true,true, true); 149 | _baFlvEncoder.start(); 150 | 151 | // Make FLV: 152 | 153 | for (var i:int = 0; i < _bitmaps.length; i++) 154 | { 155 | var audioChunk:ByteArray = new ByteArray(); 156 | audioChunk.writeBytes(baAudio, i * _baFlvEncoder.audioFrameSize, _baFlvEncoder.audioFrameSize); 157 | 158 | _baFlvEncoder.addFrame(_bitmaps[i], audioChunk); 159 | 160 | _bitmaps[i].dispose(); 161 | } 162 | _baFlvEncoder.updateDurationMetadata(); 163 | 164 | var tf:TextField = new TextField(); 165 | tf.autoSize = TextFieldAutoSize.LEFT; 166 | tf.defaultTextFormat = new TextFormat("_sans", 18, 0x888888, true); 167 | tf.text = "Click to save."; 168 | this.addChild(tf); 169 | this.addEventListener(MouseEvent.CLICK, onClick); 170 | } 171 | 172 | private function onClick(e:*):void 173 | { 174 | this.removeEventListener(MouseEvent.CLICK, onClick); 175 | 176 | // Save FLV file via FileReference 177 | var fileRef:FileReference = new FileReference(); 178 | fileRef.save(_baFlvEncoder.byteArray, "test.flv"); 179 | 180 | // cleanup 181 | _baFlvEncoder.kill(); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/basic/assets/hello.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeropointnine/leelib/efa5e0c78c130f21868db4311d2cc46550593661/src/leelibExamples/flvEncoder/basic/assets/hello.mp3 -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/webcam/WebcamApp.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.webcam 2 | { 3 | import flash.display.BitmapData; 4 | import flash.display.Sprite; 5 | import flash.display.StageAlign; 6 | import flash.display.StageScaleMode; 7 | import flash.events.ActivityEvent; 8 | import flash.events.Event; 9 | import flash.events.MouseEvent; 10 | import flash.events.StatusEvent; 11 | import flash.media.Camera; 12 | import flash.media.Microphone; 13 | import flash.media.Video; 14 | import flash.net.FileReference; 15 | import flash.net.NetConnection; 16 | import flash.net.NetStream; 17 | import flash.text.TextField; 18 | import flash.text.TextFormat; 19 | import flash.utils.ByteArray; 20 | import flash.utils.clearTimeout; 21 | import flash.utils.getTimer; 22 | import flash.utils.setTimeout; 23 | 24 | import leelib.util.flvEncoder.MicRecorderUtil; 25 | import leelib.util.flvEncoder.ByteArrayFlvEncoder; 26 | import leelib.util.flvEncoder.FlvEncoder; 27 | 28 | import leelibExamples.flvEncoder.webcam.uiEtc.CheckBox; 29 | import leelibExamples.flvEncoder.webcam.uiEtc.RecordButton; 30 | import leelibExamples.flvEncoder.webcam.uiEtc.States; 31 | 32 | 33 | [SWF(width="340", height="290", frameRate="60")] 34 | public class WebcamApp extends Sprite 35 | { 36 | private const OUTPUT_WIDTH:Number = 320; 37 | private const OUTPUT_HEIGHT:Number = 240; 38 | 39 | private const FLV_FRAMERATE:int = 15; 40 | 41 | private var _output:Sprite; 42 | private var _btnRecord:RecordButton; 43 | private var _tfTime:TextField; 44 | private var _tfPrompt:TextField; 45 | private var _checkboxVideo:CheckBox; 46 | private var _checkboxAudio:CheckBox; 47 | 48 | private var _camera:Camera; 49 | private var _video:Video; 50 | private var _netConnection:NetConnection; 51 | private var _ns:NetStream; 52 | private var _micUtil:MicRecorderUtil; 53 | 54 | private var _baFlvEncoder:ByteArrayFlvEncoder; 55 | private var _encodeFrameNum:int; 56 | 57 | private var _bitmaps:Array; 58 | private var _audioData:ByteArray; 59 | 60 | private var _startTime:Number; 61 | private var _timeoutId:Number; 62 | private var _state:String; 63 | 64 | 65 | public function WebcamApp() 66 | { 67 | this.stage.align = StageAlign.TOP_LEFT; 68 | this.stage.scaleMode = StageScaleMode.NO_SCALE; 69 | 70 | this.graphics.beginFill(0xe5e5e5); 71 | this.graphics.drawRect(0,0, this.stage.stageWidth,this.stage.stageHeight); 72 | this.graphics.endFill(); 73 | 74 | _output = new Sprite(); 75 | _output.graphics.beginFill(0xe5e5e5); 76 | _output.graphics.drawRect(0,0, OUTPUT_WIDTH, OUTPUT_HEIGHT); 77 | _output.graphics.endFill(); 78 | _output.x = 10; 79 | _output.y = 10; 80 | this.addChild(_output); 81 | 82 | _btnRecord = new RecordButton(); 83 | _btnRecord.addEventListener(MouseEvent.CLICK, onBtnRecClick); 84 | _btnRecord.x = 10; 85 | _btnRecord.y = _output.y + _output.height + 10; 86 | this.addChild(_btnRecord); 87 | 88 | _checkboxVideo = new CheckBox("save video", false,false); 89 | _checkboxVideo.x = _btnRecord.x + _btnRecord.width + 10; 90 | _checkboxVideo.y = _btnRecord.y; 91 | _checkboxVideo.on = true; 92 | _checkboxVideo.addEventListener(MouseEvent.CLICK, onCkVideo); 93 | this.addChild(_checkboxVideo); 94 | 95 | _checkboxAudio = new CheckBox("save audio", false,false); 96 | _checkboxAudio.x = _checkboxVideo.x + _checkboxVideo.width + 5; 97 | _checkboxAudio.y = _btnRecord.y; 98 | _checkboxAudio.on = true; 99 | _checkboxAudio.addEventListener(MouseEvent.CLICK, onCkAudio); 100 | this.addChild(_checkboxAudio); 101 | 102 | _tfTime = new TextField(); 103 | with (_tfTime) 104 | { 105 | defaultTextFormat = new TextFormat("_sans", 12, 0x0, null,null,null,null,null, "right"); 106 | width = 100; 107 | height = 20; 108 | selectable = mouseEnabled = false; 109 | x = _output.x + _output.width - _tfTime.width; 110 | y = _btnRecord.y; 111 | } 112 | this.addChild(_tfTime); 113 | 114 | _tfPrompt = new TextField(); 115 | with (_tfPrompt) 116 | { 117 | defaultTextFormat = new TextFormat("_sans", 36, 0x0, null,null,null,null,null, "center"); 118 | width = _output.width; 119 | height = 100; 120 | text = " "; 121 | x = _output.x; 122 | y = _output.y + (_output.height - _tfPrompt.textHeight) / 2; 123 | selectable = mouseEnabled = false; 124 | } 125 | this.addChild(_tfPrompt); 126 | 127 | // 128 | 129 | _video = new Video(); 130 | _output.addChild(_video); 131 | 132 | _netConnection = new NetConnection(); 133 | _netConnection.connect(null); 134 | _ns = new NetStream(_netConnection); 135 | 136 | _camera = Camera.getCamera(); 137 | _camera.setMode(320,240, 30); 138 | _camera.setQuality(0, 100); 139 | 140 | // 141 | 142 | var mic:Microphone = Microphone.getMicrophone(); 143 | mic.setSilenceLevel(0, int.MAX_VALUE); 144 | mic.gain = 100; 145 | mic.rate = 44; 146 | _micUtil = new MicRecorderUtil(mic); 147 | 148 | setState(States.WAITING_FOR_WEBCAM); 149 | } 150 | 151 | private function onCamStatus($e:StatusEvent):void 152 | { 153 | if ($e.code == "Camera.Unmuted") // rem: this event can't be relied upon 154 | { 155 | _camera.removeEventListener(StatusEvent.STATUS, onCamStatus); 156 | _camera.removeEventListener(ActivityEvent.ACTIVITY, onCamActivity); 157 | setState(States.WAITING_FOR_RECORD); 158 | } 159 | } 160 | private function onCamActivity($e:ActivityEvent):void 161 | { 162 | _camera.removeEventListener(StatusEvent.STATUS, onCamStatus); 163 | _camera.removeEventListener(ActivityEvent.ACTIVITY, onCamActivity); 164 | setState(States.WAITING_FOR_RECORD); 165 | } 166 | 167 | // 168 | 169 | private function startRecording():void 170 | { 171 | _bitmaps = new Array(); 172 | 173 | _micUtil.record(); 174 | 175 | _startTime = getTimer(); 176 | captureFrame(); 177 | } 178 | 179 | private function startEncoding():void 180 | { 181 | // Get just a little more Mic input! 182 | // (or enough time for last chunk of data to come in?) 183 | setTimeout(startEncoding_2, 200); 184 | } 185 | 186 | private function startEncoding_2():void 187 | { 188 | _micUtil.stop(); 189 | 190 | _audioData = _micUtil.byteArray; 191 | 192 | // Make FlvEncoder object 193 | _baFlvEncoder = new ByteArrayFlvEncoder(FLV_FRAMERATE); 194 | if (_checkboxVideo.on) 195 | { 196 | _baFlvEncoder.setVideoProperties(OUTPUT_WIDTH,OUTPUT_HEIGHT); 197 | 198 | /* 199 | Replace the line above with the following to use the (much faster) Alchemy encoder. 200 | Requires Flash 10 and addition of of "leelib/util/flvEncoder/alchemy/" 201 | to the project library path. 202 | 203 | _baFlvEncoder.setVideoProperties(OUTPUT_WIDTH,OUTPUT_HEIGHT, VideoPayloadMakerAlchemy); 204 | */ 205 | } 206 | if (_checkboxAudio.on) 207 | { 208 | _baFlvEncoder.setAudioProperties(FlvEncoder.SAMPLERATE_44KHZ, true, false, true); 209 | } 210 | _baFlvEncoder.start(); 211 | 212 | _encodeFrameNum = -1; 213 | this.addEventListener(Event.ENTER_FRAME, onEnterFrameEncode); 214 | // ... encode FLV frames on an interval to keep UI from locking up 215 | } 216 | 217 | private function onEnterFrameEncode(e:*):void 218 | { 219 | // Encode 3 frames per iteration 220 | for (var i:int = 0; i < 3; i++) 221 | { 222 | _encodeFrameNum++; 223 | 224 | if (_encodeFrameNum < _bitmaps.length) { 225 | encodeNextFrame(); 226 | } 227 | else { 228 | // done 229 | this.removeEventListener(Event.ENTER_FRAME, onEnterFrameEncode); 230 | _baFlvEncoder.updateDurationMetadata(); 231 | setState(States.SAVING); 232 | return; 233 | } 234 | } 235 | 236 | _tfPrompt.text = "encoding\r" + (_encodeFrameNum+1) + " of " + _bitmaps.length; 237 | } 238 | 239 | private function encodeNextFrame():void 240 | { 241 | var baAudio:ByteArray; 242 | var bmdVideo:BitmapData; 243 | 244 | if (_checkboxAudio.on) 245 | { 246 | baAudio = new ByteArray(); 247 | var pos:int = _encodeFrameNum * _baFlvEncoder.audioFrameSize; 248 | 249 | if (pos < 0 || pos + _baFlvEncoder.audioFrameSize > _audioData.length) { 250 | trace('out of bounds:', _encodeFrameNum, pos + _baFlvEncoder.audioFrameSize, 'versus', _audioData); 251 | baAudio.length = _baFlvEncoder.audioFrameSize; // zero's 252 | } 253 | else { 254 | baAudio.writeBytes(_audioData, pos, _baFlvEncoder.audioFrameSize); 255 | } 256 | } 257 | 258 | if (_checkboxVideo.on) 259 | { 260 | bmdVideo = _bitmaps[_encodeFrameNum]; 261 | } 262 | 263 | _baFlvEncoder.addFrame(bmdVideo, baAudio); 264 | 265 | // Video frame has been encoded, so we can discard it now 266 | _bitmaps[_encodeFrameNum].dispose(); 267 | } 268 | 269 | private function captureFrame():void 270 | { 271 | // capture frame 272 | var b:BitmapData = new BitmapData(OUTPUT_WIDTH,OUTPUT_HEIGHT,false,0x0); 273 | b.draw(_output); 274 | _bitmaps.push(b); 275 | 276 | var sec:int = int(_bitmaps.length / FLV_FRAMERATE); 277 | _tfTime.text = "0:" + ((sec < 10) ? ("0" + sec) : sec); 278 | 279 | // end condition: 280 | if (_bitmaps.length / FLV_FRAMERATE >= 10.0) { 281 | setState(States.ENCODING); 282 | return; 283 | } 284 | 285 | // schedule next captureFrame 286 | var elapsedMs:int = getTimer() - _startTime; 287 | var nextMs:int = (_bitmaps.length / FLV_FRAMERATE) * 1000; 288 | var deltaMs:int = nextMs - elapsedMs; 289 | if (deltaMs < 10) deltaMs = 10; 290 | _timeoutId = setTimeout(captureFrame, deltaMs); 291 | } 292 | 293 | private function onBtnRecClick(e:*):void 294 | { 295 | if (_state == States.WAITING_FOR_RECORD) 296 | setState(States.RECORDING); 297 | else if (_state == States.RECORDING) 298 | setState(States.ENCODING); 299 | } 300 | 301 | private function onCkVideo(e:*):void 302 | { 303 | if (_checkboxVideo.on && ! _checkboxAudio.on) return; 304 | _checkboxVideo.on = ! _checkboxVideo.on; 305 | } 306 | 307 | private function onCkAudio(e:*):void 308 | { 309 | if (_checkboxAudio.on && ! _checkboxVideo.on) return; 310 | _checkboxAudio.on = ! _checkboxAudio.on; 311 | } 312 | 313 | private function onThisClickSave(e:*):void 314 | { 315 | var fileRef:FileReference = new FileReference(); 316 | fileRef.save(_baFlvEncoder.byteArray, "no_server_required.flv"); 317 | 318 | setState(States.WAITING_FOR_RECORD); 319 | } 320 | 321 | // 322 | 323 | private function setState($state:String):void 324 | { 325 | _state = $state; 326 | 327 | switch (_state) 328 | { 329 | case States.WAITING_FOR_WEBCAM: 330 | _camera.addEventListener(StatusEvent.STATUS, onCamStatus); 331 | _camera.addEventListener(ActivityEvent.ACTIVITY, onCamActivity); 332 | _video.attachCamera(_camera); 333 | break; 334 | 335 | case States.WAITING_FOR_RECORD: 336 | _video.alpha = 1.0; 337 | _video.attachCamera(_camera); 338 | _btnRecord.visible = true; 339 | _btnRecord.showRecord(); 340 | _tfTime.text = ""; 341 | _tfPrompt.visible = false; 342 | _checkboxVideo.enabled = _checkboxAudio.enabled = true; 343 | break; 344 | 345 | case States.RECORDING: 346 | _btnRecord.showStop(); 347 | _checkboxVideo.enabled = _checkboxAudio.enabled = false; 348 | startRecording(); 349 | break; 350 | 351 | case States.ENCODING: 352 | clearTimeout(_timeoutId); 353 | _video.alpha = 0.5; 354 | _tfPrompt.text = "encoding"; 355 | _tfPrompt.visible = true; 356 | _btnRecord.visible = false; 357 | startEncoding(); 358 | break; 359 | 360 | case States.SAVING: 361 | _tfPrompt.text = "< click to save >"; 362 | _tfPrompt.visible = true; 363 | this.addEventListener(MouseEvent.CLICK, onThisClickSave); 364 | break; 365 | } 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/webcam/uiEtc/CheckBox.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.webcam.uiEtc 2 | { 3 | import flash.display.Sprite; 4 | import flash.events.MouseEvent; 5 | import flash.text.TextField; 6 | import flash.text.TextFieldAutoSize; 7 | import flash.text.TextFormat; 8 | 9 | public class CheckBox extends Sprite 10 | { 11 | private var _sprBg:Sprite; 12 | private var _sprOn:Sprite; 13 | private var _isOn:Boolean; 14 | 15 | 16 | public function CheckBox($text:String, $bold:Boolean, $italic:Boolean) 17 | { 18 | _sprBg = new Sprite(); 19 | _sprBg.graphics.beginFill(0xffffff); 20 | _sprBg.graphics.lineStyle(1, 0x0); 21 | _sprBg.graphics.drawRect(4,4,12,12); 22 | _sprBg.graphics.endFill(); 23 | this.addChild(_sprBg); 24 | 25 | _sprOn = new Sprite(); 26 | _sprOn.graphics.beginFill(0x0); 27 | _sprOn.graphics.lineStyle(1, 0x0); 28 | _sprOn.graphics.drawRect(6,6,8,8); 29 | _sprOn.graphics.endFill(); 30 | _sprBg.addChild(_sprOn); 31 | 32 | var tf:TextField = new TextField(); 33 | with (tf) 34 | { 35 | defaultTextFormat = new TextFormat("_sans", 10, 0x0, $bold, $italic,null,null,null,"center"); 36 | width = 80; 37 | height = 18; 38 | autoSize = TextFieldAutoSize.LEFT; 39 | selectable = mouseEnabled = false; 40 | x = this.width + 5; 41 | y = 2; 42 | text = $text; 43 | } 44 | this.addChild(tf); 45 | 46 | this.addEventListener(MouseEvent.ROLL_OVER, onOver); 47 | this.addEventListener(MouseEvent.ROLL_OUT, onOut); 48 | this.buttonMode = true; 49 | 50 | on = false; 51 | } 52 | 53 | public function set enabled($b:Boolean):void 54 | { 55 | this.mouseEnabled = this.mouseChildren = $b; 56 | } 57 | 58 | 59 | public function get on():Boolean 60 | { 61 | return _isOn; 62 | } 63 | public function set on($b:Boolean):void 64 | { 65 | _isOn = $b; 66 | _sprOn.visible = _isOn; 67 | } 68 | 69 | private function onOver(e:*):void 70 | { 71 | _sprBg.alpha = 0.66; 72 | } 73 | private function onOut(e:*):void 74 | { 75 | _sprBg.alpha = 1.0; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/webcam/uiEtc/MinimalFlvPlayback.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.webcam.uiEtc 2 | { 3 | import flash.display.Sprite; 4 | import flash.media.Video; 5 | import flash.net.NetConnection; 6 | import flash.net.NetStream; 7 | 8 | public class MinimalFlvPlayback extends Sprite 9 | { 10 | private var _video:Video; 11 | private var _nc:NetConnection; 12 | private var _ns:NetStream; 13 | 14 | public function MinimalFlvPlayback() 15 | { 16 | _video = new Video(); 17 | this.addChild(_video); 18 | 19 | _nc = new NetConnection(); 20 | _nc.connect(null); 21 | 22 | _ns = new NetStream(_nc); 23 | _ns.client = this; 24 | 25 | _video.attachNetStream(_ns); 26 | 27 | _ns.play("c:/no_server_required.flv"); 28 | } 29 | 30 | public function onMetaData($o:Object):void 31 | { 32 | for each (var s:String in $o) { 33 | trace(s, $o[s]); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/webcam/uiEtc/RecordButton.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.webcam.uiEtc 2 | { 3 | import flash.display.Sprite; 4 | import flash.events.MouseEvent; 5 | import flash.text.TextField; 6 | import flash.text.TextFormat; 7 | 8 | public class RecordButton extends Sprite 9 | { 10 | private var _tf:TextField; 11 | 12 | 13 | public function RecordButton() 14 | { 15 | this.graphics.beginFill(0xffffff); 16 | this.graphics.lineStyle(1, 0x0); 17 | this.graphics.drawRect(0,0,100,20); 18 | this.graphics.endFill(); 19 | 20 | _tf = new TextField(); 21 | with (_tf) 22 | { 23 | defaultTextFormat = new TextFormat("_sans", 12, 0x0, true, null,null,null,null,"center"); 24 | width = 100; 25 | height = 18; 26 | selectable = mouseEnabled = false; 27 | x = 0; 28 | y = 0; 29 | } 30 | this.addChild(_tf); 31 | 32 | this.addEventListener(MouseEvent.ROLL_OVER, onOver); 33 | this.addEventListener(MouseEvent.ROLL_OUT, onOut); 34 | this.buttonMode = true; 35 | 36 | showRecord(); 37 | } 38 | 39 | public function showRecord():void 40 | { 41 | _tf.text = "record"; 42 | } 43 | 44 | public function showStop():void 45 | { 46 | _tf.text = "stop"; 47 | } 48 | 49 | private function onOver(e:*):void 50 | { 51 | this.alpha = 0.66; 52 | } 53 | private function onOut(e:*):void 54 | { 55 | this.alpha = 1.0; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/leelibExamples/flvEncoder/webcam/uiEtc/States.as: -------------------------------------------------------------------------------- 1 | package leelibExamples.flvEncoder.webcam.uiEtc 2 | { 3 | public class States 4 | { 5 | public static const WAITING_FOR_WEBCAM:String = "waitingForWebcam"; 6 | public static const WAITING_FOR_RECORD:String = "waiting"; 7 | public static const RECORDING:String = "recording"; 8 | public static const ENCODING:String = "encoding"; 9 | public static const SAVING:String = "waitingForClick"; 10 | 11 | // saving to file is synchronous so needs no 'state' 12 | } 13 | } --------------------------------------------------------------------------------