package com.kaltura.hls.m2ts
{
    import flash.utils.ByteArray;
    import flash.net.ObjectEncoding;
    import flash.utils.ByteArray;
    import flash.utils.Endian;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    import com.hurlant.util.Hex;

    /**
     * Process packetized elementary streams and extract NALUs and other data.
     */
    public class PESProcessor
    {
        public var types:Object = {};
        public var streams:Object = {};

        public var lastVideoNALU:NALU = null;

        public var transcoder:FLVTranscoder = new FLVTranscoder();

        public var headerSent:Boolean = false;

        public var pmtStreamId:int = -1;

        protected var pendingBuffers:Vector.<Object> = new Vector.<Object>();
        protected var pendingLastConvertedIndex:int = 0;

        public function logStreams():void
        {
            trace("----- PES state -----");
            for(var k:* in streams)
            {
                trace("   " + k + " has " + streams[k].buffer.length + " bytes, type=" + types[k]);
            }
        }

        public function clear(clearAACConfig:Boolean = true):void
        {
            streams = {};
            lastVideoNALU = null;
            transcoder.clear(clearAACConfig);
        }

        private function parseProgramAssociationTable(bytes:ByteArray, cursor:uint):Boolean
        {
            // Get the section length.
            var sectionLen:uint = ((bytes[cursor+2] & 0x03) << 8) | bytes[cursor+3];

            // Check the section length for a single PMT.
            if (sectionLen > 13)
                trace("Saw multiple PMT entries in the PAT; blindly choosing first one.");

            // Grab the PMT ID.
            pmtStreamId = ((bytes[cursor+10] << 8) | bytes[cursor+11]) & 0x1FFF;
            trace("Saw PMT ID of " + pmtStreamId);

            return true;
        }

        private function parseProgramMapTable(bytes:ByteArray, cursor:uint):Boolean
        {
            var sectionLength:uint;
            var sectionLimit:uint;
            var programInfoLength:uint;
            var type:uint;
            var pid:uint;
            var esInfoLength:uint;
            var seenPIDsByClass:Array;
            var mediaClass:int;

            var hasAudio:Boolean = false, hasVideo:Boolean = false;

            // Set up types.
            types = [];
            seenPIDsByClass = [];
            seenPIDsByClass[MediaClass.VIDEO] = Infinity;
            seenPIDsByClass[MediaClass.AUDIO] = Infinity;

            // Process section length and limit.
            cursor++;

            sectionLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
            cursor += 2;

            if(sectionLength + cursor > bytes.length)
            {
                trace("Not enough data to read. 1");
                return false;
            }

            // Skip a few things we don't care about: program number, RSV, version, CNI, section, last_section, pcr_cid
            sectionLimit = cursor + sectionLength;
            cursor += 7;

            // And get the program info length.
            programInfoLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
            cursor += 2;

            // If not enough data to proceed, bail.
            if(programInfoLength + cursor > bytes.length)
            {
                trace("Not enough data to read. 2");
                return false;
            }

            cursor += programInfoLength;

            const CRC_SIZE:int = 4;
            while(cursor < sectionLimit - CRC_SIZE)
            {
                type = bytes[cursor++];
                pid = ((bytes[cursor] & 0x1f) << 8) + bytes[cursor + 1];
                cursor += 2;

                mediaClass = MediaClass.calculate(type);

                if(mediaClass == MediaClass.VIDEO)
                    hasVideo = true

                if(mediaClass == MediaClass.AUDIO)
                    hasAudio = true

                // For video & audio, select the lowest PID for each kind.
                if(mediaClass == MediaClass.OTHER
                 || pid < seenPIDsByClass[mediaClass])
                {
                    // Clear a higher PID if present.
                    if(mediaClass != MediaClass.OTHER
                     && seenPIDsByClass[mediaClass] < Infinity)
                        types[seenPIDsByClass[mediaClass]] = -1;

                    types[pid] = type;
                    seenPIDsByClass[mediaClass] = pid;
                }

                // Skip the esInfo data.
                esInfoLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
                cursor += 2;
                cursor += esInfoLength;
            }

            // Cook out header to transcoder.
            //transcoder.writeHeader(hasAudio, hasVideo);
            headerSent = true;

            return true;
        }

        public function append(packet:PESPacket):Boolean
        {
//            trace("saw packet of " + packet.buffer.length);
            var b:ByteArray = packet.buffer;
            b.position = 0;

            if(b.length < 8)
            {
                trace("Ignoring too short PES packet, length=" + b.length);
                return true;
            }

            // Get the start code.
            var startCode:uint = b.readUnsignedInt();
            if((startCode & 0xFFFFFF00) != 0x00000100)
            {
                // It could be a program association table.
                if((startCode & 0xFFFFFF00) == 0x0000b000)
                {
                    parseProgramAssociationTable(b, 1);
                    return true;
                }

                // It could be the program map table.
                if((startCode & 0xFFFFFC00) == 0x0002b000)
                {
                    parseProgramMapTable(b, 1);
                    return true;
                }

                var tmp:ByteArray = new ByteArray();
                tmp.writeInt(startCode);
                trace("ES prefix was wrong, expected 00:00:01:xx but got " + Hex.fromArray(tmp, true));
                return true;
            }

            // Get the stream ID.
            var streamID:int = startCode & 0xFF;

            // Get the length.
            var packetLength:uint = b.readUnsignedShort();
            if(packetLength)
            {
                if(b.length < packetLength )
                {
                    trace("WARNING: parsePESPacket - not enough bytes, expecting " + packetLength + ", but have " + b.length);
                    return false; // not enough bytes in packet
                }
            }

            if(b.length < 9)
            {
                trace("WARNING: parsePESPacket - too short to read header!");
                return false;
            }

            // Read the rest of the header.
            var cursor:uint = 6;
            var dataAlignment:Boolean = (b[cursor] & 0x04) != 0;
            cursor++;

            var ptsDts:uint = (b[cursor] & 0xc0) >> 6;
            cursor++;

            var pesHeaderDataLength:uint = b[cursor];
            cursor++;

            //trace(" PES align=" + dataAlignment + " ptsDts=" + ptsDts + " header=" + pesHeaderDataLength);

            var pts:Number = 0, dts:Number = 0;

            if(ptsDts & 0x02)
            {
                // has PTS at least
                if(cursor + 5 > b.length)
                    return true;

                pts  = b[cursor] & 0x0e;
                pts *= 128;
                pts += b[cursor + 1];
                pts *= 256;
                pts += b[cursor + 2] & 0xfe;
                pts *= 128;
                pts += b[cursor + 3];
                pts *= 256;
                pts += b[cursor + 4] & 0xfe;
                pts /= 2;

                if(ptsDts & 0x01)
                {
                    // DTS too!
                    if(cursor + 10 > b.length)
                        return true;

                    dts  = b[cursor + 5] & 0x0e;
                    dts *= 128;
                    dts += b[cursor + 6];
                    dts *= 256;
                    dts += b[cursor + 7] & 0xfe;
                    dts *= 128;
                    dts += b[cursor + 8];
                    dts *= 256;
                    dts += b[cursor + 9] & 0xfe;
                    dts /= 2;
                }
                else
                    dts = pts;
            }

            packet.pts = pts;
            packet.dts = dts;
            //trace("   PTS=" + pts/90000 + " DTS=" + dts/90000);

            cursor += pesHeaderDataLength;

            if(cursor > b.length)
            {
                trace("WARNING: parsePESPacket - ran out of bytes");
                return true;
            }

            if(types[packet.packetID] == undefined)
            {
                trace("WARNING: parsePESPacket - unknown type");
                return true;
            }

            var pes:PESPacketStream;

            if(streams[packet.packetID] == undefined)
            {
                if(dts < 0.0)
                {
                    trace("WARNING: parsePESPacket - invalid decode timestamp, skipping");
                    return true;
                }

                pes = new PESPacketStream();
                streams[packet.packetID] = pes;
            }
            else
            {
                pes = streams[packet.packetID];
            }

            if(headerSent == false)
            {
                trace("Skipping data that came before PMT");
                return true;
            }

            // Note the type at this moment in time.
            packet.type = types[packet.packetID];
            packet.headerLength = cursor;

            // And process.
            if(MediaClass.calculate(types[packet.packetID]) == MediaClass.VIDEO)
            {
                var start:int = NALU.scan(b, cursor, true);
                if(start == -1 && lastVideoNALU)
                {
                    trace("Stuff entire " + (b.length - cursor) + " into previous NALU.");
                    lastVideoNALU.buffer.position = lastVideoNALU.buffer.length;
                    b.position = 0;
                    lastVideoNALU.buffer.writeBytes(b, cursor, b.length - cursor);
                    return true;

                }
                else if((start - cursor) > 0 && lastVideoNALU)
                {
                    // Shove into previous buffer.
                    trace("Stuffing first " + (start - cursor) + " bytes into previous NALU.");
                    lastVideoNALU.buffer.position = lastVideoNALU.buffer.length;
                    b.position = 0;
                    lastVideoNALU.buffer.writeBytes(b, cursor, start - cursor);
                    cursor = start;
                }

                // Submit previous data.
                if(lastVideoNALU)
                {
                    pendingBuffers.push(lastVideoNALU.clone());
                }

                // Update NALU state.
                lastVideoNALU = new NALU();
                lastVideoNALU.buffer = new ByteArray();
                lastVideoNALU.pts = pts;
                lastVideoNALU.dts = dts;
                lastVideoNALU.type = packet.type;
                lastVideoNALU.buffer.writeBytes(b, cursor);

            }
            else if(types[packet.packetID] == 0x0F)
            {
                // It's an AAC stream.
                //transcoder.convertAAC(packet);
                pendingBuffers.push(packet.clone());
            }
            else if(types[packet.packetID] == 0x03 || types[packet.packetID] == 0x04)
            {
                // It's an MP3 stream. Pass through directly.
                //transcoder.convertMP3(packet);
                pendingBuffers.push(packet.clone());
            }
            else
            {
                trace("Unknown packet ID type " + types[packet.packetID] + ", ignoring (A).");
            }

            bufferPendingNalus();

            return true;
        }

        /**
         * To avoid transcoding all content on flush, we buffer it into FLV
         * tags as we go. However, they remain undelivered until we can gather
         * final SPS/PPS information. This method is responsible for
         * incrementally buffering in the FLV transcoder as we go.
         */
        public function bufferPendingNalus():void
        {
            // Iterate and buffer new NALUs.
            for(var i:int=pendingLastConvertedIndex; i<pendingBuffers.length; i++)
            {
                if(pendingBuffers[i] is NALU)
                {
                    transcoder.convert(pendingBuffers[i] as NALU);
                }
                else if(pendingBuffers[i] is PESPacket)
                {
                    var packet:PESPacket = pendingBuffers[i] as PESPacket;
                    if(packet.type == 0x0F)
                    {
                        // It's an AAC stream.
                        transcoder.convertAAC(packet);
                    }
                    else if(packet.type == 0x03 || packet.type == 0x04)
                    {
                        // It's an MP3 stream. Pass through directly.
                        transcoder.convertMP3(packet);
                    }
                    else
                    {
                        trace("Unknown packet ID type " + packet.type + ", ignoring (B).");
                    }
                }
            }

            // Note the last item we converted so we can avoid duplicating work.
            pendingLastConvertedIndex = pendingBuffers.length;
        }

        public function processAllNalus():void
        {
            // Consume any unposted video NALUs.
            if(lastVideoNALU)
            {
                pendingBuffers.push(lastVideoNALU.clone());
                lastVideoNALU = null;
            }

            // First walk all the video NALUs and get the correct SPS/PPS
            if(pendingBuffers.length == 0)
                return;

            // Then emit SPS/PPS
            transcoder.emitSPSPPSUnbuffered();

            // Complete buffering and emit it all.
            bufferPendingNalus();
            transcoder.emitBufferedTags();

            // Don't forget to clear the pending list.
            pendingBuffers.length = 0;
            pendingLastConvertedIndex = 0;
        }
    }
}

package com.kaltura.hls.m2ts
{
    import flash.utils.ByteArray;
    import flash.net.ObjectEncoding;
    import flash.utils.ByteArray;
    import flash.utils.Endian;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    import com.hurlant.util.Hex;

    /**
     * Process packetized elementary streams and extract NALUs and other data.
     */
    public class PESProcessor
    {
        public var types:Object = {};
        public var streams:Object = {};

        public var lastVideoNALU:NALU = null;

        public var transcoder:FLVTranscoder = new FLVTranscoder();

        public var headerSent:Boolean = false;

        public var pmtStreamId:int = -1;

        protected var pendingBuffers:Vector.<Object> = new Vector.<Object>();
        protected var pendingLastConvertedIndex:int = 0;

        public function logStreams():void
        {
            trace("----- PES state -----");
            for(var k:* in streams)
            {
                trace("   " + k + " has " + streams[k].buffer.length + " bytes, type=" + types[k]);
            }
        }

        public function clear(clearAACConfig:Boolean = true):void
        {
            streams = {};
            lastVideoNALU = null;
            transcoder.clear(clearAACConfig);
        }

        private function parseProgramAssociationTable(bytes:ByteArray, cursor:uint):Boolean
        {
            // Get the section length.
            var sectionLen:uint = ((bytes[cursor+2] & 0x03) << 8) | bytes[cursor+3];

            // Check the section length for a single PMT.
            if (sectionLen > 13)
                trace("Saw multiple PMT entries in the PAT; blindly choosing first one.");

            // Grab the PMT ID.
            pmtStreamId = ((bytes[cursor+10] << 8) | bytes[cursor+11]) & 0x1FFF;
            trace("Saw PMT ID of " + pmtStreamId);

            return true;
        }

        private function parseProgramMapTable(bytes:ByteArray, cursor:uint):Boolean
        {
            var sectionLength:uint;
            var sectionLimit:uint;
            var programInfoLength:uint;
            var type:uint;
            var pid:uint;
            var esInfoLength:uint;
            var seenPIDsByClass:Array;
            var mediaClass:int;

            var hasAudio:Boolean = false, hasVideo:Boolean = false;

            // Set up types.
            types = [];
            seenPIDsByClass = [];
            seenPIDsByClass[MediaClass.VIDEO] = Infinity;
            seenPIDsByClass[MediaClass.AUDIO] = Infinity;

            // Process section length and limit.
            cursor++;

            sectionLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
            cursor += 2;

            if(sectionLength + cursor > bytes.length)
            {
                trace("Not enough data to read. 1");
                return false;
            }

            // Skip a few things we don't care about: program number, RSV, version, CNI, section, last_section, pcr_cid
            sectionLimit = cursor + sectionLength;
            cursor += 7;

            // And get the program info length.
            programInfoLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
            cursor += 2;

            // If not enough data to proceed, bail.
            if(programInfoLength + cursor > bytes.length)
            {
                trace("Not enough data to read. 2");
                return false;
            }

            cursor += programInfoLength;

            const CRC_SIZE:int = 4;
            while(cursor < sectionLimit - CRC_SIZE)
            {
                type = bytes[cursor++];
                pid = ((bytes[cursor] & 0x1f) << 8) + bytes[cursor + 1];
                cursor += 2;

                mediaClass = MediaClass.calculate(type);

                if(mediaClass == MediaClass.VIDEO)
                    hasVideo = true

                if(mediaClass == MediaClass.AUDIO)
                    hasAudio = true

                // For video & audio, select the lowest PID for each kind.
                if(mediaClass == MediaClass.OTHER
                 || pid < seenPIDsByClass[mediaClass])
                {
                    // Clear a higher PID if present.
                    if(mediaClass != MediaClass.OTHER
                     && seenPIDsByClass[mediaClass] < Infinity)
                        types[seenPIDsByClass[mediaClass]] = -1;

                    types[pid] = type;
                    seenPIDsByClass[mediaClass] = pid;
                }

                // Skip the esInfo data.
                esInfoLength = ((bytes[cursor] & 0x0f) << 8) + bytes[cursor + 1];
                cursor += 2;
                cursor += esInfoLength;
            }

            // Cook out header to transcoder.
            //transcoder.writeHeader(hasAudio, hasVideo);
            headerSent = true;

            return true;
        }

        public function append(packet:PESPacket):Boolean
        {
//            trace("saw packet of " + packet.buffer.length);
            var b:ByteArray = packet.buffer;
            b.position = 0;

            if(b.length < 8)
            {
                trace("Ignoring too short PES packet, length=" + b.length);
                return true;
            }

            // Get the start code.
            var startCode:uint = b.readUnsignedInt();
            if((startCode & 0xFFFFFF00) != 0x00000100)
            {
                // It could be a program association table.
                if((startCode & 0xFFFFFF00) == 0x0000b000)
                {
                    parseProgramAssociationTable(b, 1);
                    return true;
                }

                // It could be the program map table.
                if((startCode & 0xFFFFFC00) == 0x0002b000)
                {
                    parseProgramMapTable(b, 1);
                    return true;
                }

                var tmp:ByteArray = new ByteArray();
                tmp.writeInt(startCode);
                trace("ES prefix was wrong, expected 00:00:01:xx but got " + Hex.fromArray(tmp, true));
                return true;
            }

            // Get the stream ID.
            var streamID:int = startCode & 0xFF;

            // Get the length.
            var packetLength:uint = b.readUnsignedShort();
            if(packetLength)
            {
                if(b.length < packetLength )
                {
                    trace("WARNING: parsePESPacket - not enough bytes, expecting " + packetLength + ", but have " + b.length);
                    return false; // not enough bytes in packet
                }
            }

            if(b.length < 9)
            {
                trace("WARNING: parsePESPacket - too short to read header!");
                return false;
            }

            // Read the rest of the header.
            var cursor:uint = 6;
            var dataAlignment:Boolean = (b[cursor] & 0x04) != 0;
            cursor++;

            var ptsDts:uint = (b[cursor] & 0xc0) >> 6;
            cursor++;

            var pesHeaderDataLength:uint = b[cursor];
            cursor++;

            //trace(" PES align=" + dataAlignment + " ptsDts=" + ptsDts + " header=" + pesHeaderDataLength);

            var pts:Number = 0, dts:Number = 0;

            if(ptsDts & 0x02)
            {
                // has PTS at least
                if(cursor + 5 > b.length)
                    return true;

                pts  = b[cursor] & 0x0e;
                pts *= 128;
                pts += b[cursor + 1];
                pts *= 256;
                pts += b[cursor + 2] & 0xfe;
                pts *= 128;
                pts += b[cursor + 3];
                pts *= 256;
                pts += b[cursor + 4] & 0xfe;
                pts /= 2;

                if(ptsDts & 0x01)
                {
                    // DTS too!
                    if(cursor + 10 > b.length)
                        return true;

                    dts  = b[cursor + 5] & 0x0e;
                    dts *= 128;
                    dts += b[cursor + 6];
                    dts *= 256;
                    dts += b[cursor + 7] & 0xfe;
                    dts *= 128;
                    dts += b[cursor + 8];
                    dts *= 256;
                    dts += b[cursor + 9] & 0xfe;
                    dts /= 2;
                }
                else
                    dts = pts;
            }

            packet.pts = pts;
            packet.dts = dts;
            //trace("   PTS=" + pts/90000 + " DTS=" + dts/90000);

            cursor += pesHeaderDataLength;

            if(cursor > b.length)
            {
                trace("WARNING: parsePESPacket - ran out of bytes");
                return true;
            }

            if(types[packet.packetID] == undefined)
            {
                trace("WARNING: parsePESPacket - unknown type");
                return true;
            }

            var pes:PESPacketStream;

            if(streams[packet.packetID] == undefined)
            {
                if(dts < 0.0)
                {
                    trace("WARNING: parsePESPacket - invalid decode timestamp, skipping");
                    return true;
                }

                pes = new PESPacketStream();
                streams[packet.packetID] = pes;
            }
            else
            {
                pes = streams[packet.packetID];
            }

            if(headerSent == false)
            {
                trace("Skipping data that came before PMT");
                return true;
            }

            // Note the type at this moment in time.
            packet.type = types[packet.packetID];
            packet.headerLength = cursor;

            // And process.
            if(MediaClass.calculate(types[packet.packetID]) == MediaClass.VIDEO)
            {
                var start:int = NALU.scan(b, cursor, true);
                if(start == -1 && lastVideoNALU)
                {
                    trace("Stuff entire " + (b.length - cursor) + " into previous NALU.");
                    lastVideoNALU.buffer.position = lastVideoNALU.buffer.length;
                    b.position = 0;
                    lastVideoNALU.buffer.writeBytes(b, cursor, b.length - cursor);
                    return true;

                }
                else if((start - cursor) > 0 && lastVideoNALU)
                {
                    // Shove into previous buffer.
                    trace("Stuffing first " + (start - cursor) + " bytes into previous NALU.");
                    lastVideoNALU.buffer.position = lastVideoNALU.buffer.length;
                    b.position = 0;
                    lastVideoNALU.buffer.writeBytes(b, cursor, start - cursor);
                    cursor = start;
                }

                // If it's identical timestamps, accumulate it into the current unit and keep going.
                if(lastVideoNALU && pts == lastVideoNALU.pts && dts == lastVideoNALU.dts)
                {
                    CONFIG::LOGGING
                    {
                        logger.debug("Combining " + (start-cursor) + " bytes into previous NALU due to matching DTS/PTS.");
                    }
                    lastVideoNALU.buffer.position = lastVideoNALU.buffer.length;
                    b.position = 0;
                    lastVideoNALU.buffer.writeBytes(b, cursor, start - cursor);
                    cursor = start;
                }
                else
                {
                    // Submit previous data.
                    if(lastVideoNALU)
                    {
                        pendingBuffers.push(lastVideoNALU.clone());
                    }

                    // Update NALU state.
                    lastVideoNALU = new NALU();
                    lastVideoNALU.buffer = new ByteArray();
                    lastVideoNALU.pts = pts;
                    lastVideoNALU.dts = dts;
                    lastVideoNALU.type = packet.type;
                    lastVideoNALU.buffer.writeBytes(b, cursor);

                }
            }
            else if(types[packet.packetID] == 0x0F)
            {
                // It's an AAC stream.
                //transcoder.convertAAC(packet);
                pendingBuffers.push(packet.clone());
            }
            else if(types[packet.packetID] == 0x03 || types[packet.packetID] == 0x04)
            {
                // It's an MP3 stream. Pass through directly.
                //transcoder.convertMP3(packet);
                pendingBuffers.push(packet.clone());
            }
            else
            {
                trace("Unknown packet ID type " + types[packet.packetID] + ", ignoring (A).");
            }

            bufferPendingNalus();

            return true;
        }

        /**
         * To avoid transcoding all content on flush, we buffer it into FLV
         * tags as we go. However, they remain undelivered until we can gather
         * final SPS/PPS information. This method is responsible for
         * incrementally buffering in the FLV transcoder as we go.
         */
        public function bufferPendingNalus():void
        {
            // Iterate and buffer new NALUs.
            for(var i:int=pendingLastConvertedIndex; i<pendingBuffers.length; i++)
            {
                if(pendingBuffers[i] is NALU)
                {
                    transcoder.convert(pendingBuffers[i] as NALU);
                }
                else if(pendingBuffers[i] is PESPacket)
                {
                    var packet:PESPacket = pendingBuffers[i] as PESPacket;
                    if(packet.type == 0x0F)
                    {
                        // It's an AAC stream.
                        transcoder.convertAAC(packet);
                    }
                    else if(packet.type == 0x03 || packet.type == 0x04)
                    {
                        // It's an MP3 stream. Pass through directly.
                        transcoder.convertMP3(packet);
                    }
                    else
                    {
                        trace("Unknown packet ID type " + packet.type + ", ignoring (B).");
                    }
                }
            }

            // Note the last item we converted so we can avoid duplicating work.
            pendingLastConvertedIndex = pendingBuffers.length;
        }

        public function processAllNalus():void
        {
            // Consume any unposted video NALUs.
            if(lastVideoNALU)
            {
                pendingBuffers.push(lastVideoNALU.clone());
                lastVideoNALU = null;
            }

            // First walk all the video NALUs and get the correct SPS/PPS
            if(pendingBuffers.length == 0)
                return;

            // Then emit SPS/PPS
            transcoder.emitSPSPPSUnbuffered();

            // Complete buffering and emit it all.
            bufferPendingNalus();
            transcoder.emitBufferedTags();

            // Don't forget to clear the pending list.
            pendingBuffers.length = 0;
            pendingLastConvertedIndex = 0;
        }
    }
}

Combine sliced frames into a single FLV tag.

If we encounter a sliced h.264 stream, we’ll get multiple NALUs with
the same timestamps. If we present these to Flash’s h.264 decoder in
multiple FLV tags, it treats them as multiple frames and has problems
(partial frame displays, dropped “frames”). We can combine them into a
single FLV tag (based on PTS/DTS) and Flash will happily decode them.


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads
{
	import flash.display.Graphics;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The SingleLineBorderBead class draws a single line solid border.
     *  The color and thickness can be specified in CSS.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class SingleLineBorderBead implements IBead, IBorderBead, IGraphicsDrawing
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function SingleLineBorderBead()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("heightChanged", changeHandler);
            IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var styleObject:* = ValuesManager.valuesImpl.getValue(_strand,"border-color");
			var borderColor:Number = Number(styleObject);
			if( isNaN(borderColor) ) borderColor = 0x000000;
			styleObject = ValuesManager.valuesImpl.getValue(_strand,"border-thickness");
			var borderThickness:Number = Number(styleObject);
			if( isNaN(borderThickness) ) borderThickness = 1;

            var host:UIBase = UIBase(_strand);
            var g:Graphics = host.graphics;
            var w:Number = host.width;
            var h:Number = host.height;

			var gd:IGraphicsDrawing = _strand.getBeadByType(IGraphicsDrawing) as IGraphicsDrawing;
			if( this == gd ) g.clear();

			g.lineStyle();
            g.beginFill(borderColor);
            g.drawRect(0, 0, w, h);
            g.drawRect(borderThickness, borderThickness, w-2*borderThickness, h-2*borderThickness);
            g.endFill();
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads
{
	import flash.display.Graphics;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The SingleLineBorderBead class draws a single line solid border.
     *  The color and thickness can be specified in CSS.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class SingleLineBorderBead implements IBead, IBorderBead, IGraphicsDrawing
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function SingleLineBorderBead()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("heightChanged", changeHandler);
            IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
            IEventDispatcher(value).addEventListener("sizeChanged", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var styleObject:* = ValuesManager.valuesImpl.getValue(_strand,"border-color");
			var borderColor:Number = Number(styleObject);
			if( isNaN(borderColor) ) borderColor = 0x000000;
			styleObject = ValuesManager.valuesImpl.getValue(_strand,"border-thickness");
			var borderThickness:Number = Number(styleObject);
			if( isNaN(borderThickness) ) borderThickness = 1;

            var host:UIBase = UIBase(_strand);
            var g:Graphics = host.graphics;
            var w:Number = host.width;
            var h:Number = host.height;

			var gd:IGraphicsDrawing = _strand.getBeadByType(IGraphicsDrawing) as IGraphicsDrawing;
			if( this == gd ) g.clear();

			g.lineStyle();
            g.beginFill(borderColor);
            g.drawRect(0, 0, w, h);
            g.drawRect(borderThickness, borderThickness, w-2*borderThickness, h-2*borderThickness);
            g.endFill();
		}
	}
}

fix border drawing

fix border drawing


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.IBeadModel;
	import org.apache.flex.core.ILayoutChild;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParentIUIBase;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.geom.Rectangle;
	import org.apache.flex.utils.dbg.DOMPathUtil;
    import org.apache.flex.utils.CSSUtils;
    import org.apache.flex.utils.CSSContainerUtils;

    /**
     *  The HorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and vertical-align styles.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class HorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function HorizontalLayout()
		{
		}

        // the strand/host container is also an ILayoutChild because
        // can have its size dictated by the host's parent which is
        // important to know for layout optimization
		private var host:ILayoutChild;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			host = value as ILayoutChild;
		}

        /**
         * @copy org.apache.flex.core.IBeadLayout#layout
         */
		public function layout():Boolean
		{
            //trace(DOMPathUtil.getPath(host), event ? event.type : "fixed size");
			var layoutParent:ILayoutParent = host.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParentIUIBase = layoutParent.contentView;
            var padding:Rectangle = CSSContainerUtils.getPaddingMetrics(host);

			var n:int = contentView.numElements;
            var hostSizedToContent:Boolean = host.isHeightSizedToContent();
            var ilc:ILayoutChild;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			var maxHeight:Number = 0;
            // asking for contentView.height can result in infinite loop if host isn't sized already
            var h:Number = hostSizedToContent ? 0 : contentView.height;
            var w:Number = contentView.width;
			var verticalMargins:Array = [];

			for (var i:int = 0; i < n; i++)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				if (child == null || !child.visible) continue;
                var top:Number = ValuesManager.valuesImpl.getValue(child, "top");
                var bottom:Number = ValuesManager.valuesImpl.getValue(child, "bottom");
                margin = ValuesManager.valuesImpl.getValue(child, "margin");
                marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
                marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
                marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
                marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
                var ml:Number = CSSUtils.getLeftValue(marginLeft, margin, w);
                var mr:Number = CSSUtils.getRightValue(marginRight, margin, w);
                var mt:Number = CSSUtils.getTopValue(marginTop, margin, h);
                var mb:Number = CSSUtils.getBottomValue(marginBottom, margin, h);

                ilc = child as ILayoutChild;
				var lastmr:Number;
				if (marginLeft == "auto")
					ml = 0;
				if (marginRight == "auto")
					mr = 0;
                var xx:Number;
                if (i == 0)
                {
                    if (ilc)
                        ilc.setX(ml + padding.left);
                    else
                        child.x = ml + padding.left;
                }
                else
                {
                    if (ilc)
                        ilc.setX(xx + ml + lastmr);
                    else
                        child.x = xx + ml + lastmr;
                }
                if (ilc)
                {
                    if (!isNaN(ilc.percentWidth))
                        ilc.setWidth(contentView.width * ilc.percentWidth / 100, !isNaN(ilc.percentHeight));
                }
                lastmr = mr;
                var marginObject:Object = {};
                verticalMargins[i] = marginObject;
                if (!hostSizedToContent)
                {
                    // if host is sized by parent,
                    // we can position and size children horizontally now
                    setPositionAndHeight(child, top, mt, padding.top, bottom, mb, padding.bottom, h);
                    maxHeight = Math.max(maxHeight, mt + child.height + mb);
                }
                else
                {
                    if (!isNaN(top))
                    {
                        mt = top;
                        marginObject.top = mt;
                    }
                    if (!isNaN(bottom))
                    {
                        mb = bottom;
                        marginObject.bottom = mb;
                    }
                    maxHeight = Math.max(maxHeight, mt + child.height + mb);
                }
				xx = child.x + child.width;
				var valign:* = ValuesManager.valuesImpl.getValue(child, "vertical-align");
				marginObject.valign = valign;
			}
            if (hostSizedToContent)
            {
                ILayoutChild(contentView).setHeight(maxHeight, true);
                if (host.isWidthSizedToContent())
                    ILayoutChild(contentView).setWidth(xx, true);
                for (i = 0; i < n; i++)
                {
                    child = contentView.getElementAt(i) as IUIBase;
                    if (child == null || !child.visible) continue;
                    var obj:Object = verticalMargins[i];
                    setPositionAndHeight(child, obj.top, obj.marginTop, padding.top,
                        obj.bottom, obj.marginBottom, padding.bottom, maxHeight);
                }
            }
			for (i = 0; i < n; i++)
			{
				child = contentView.getElementAt(i) as IUIBase;
                ilc = child as ILayoutChild;
				if (child == null || !child.visible) continue;
                obj = verticalMargins[i];
                if (ilc)
                {
                    if (!isNaN(ilc.percentHeight))
                        ilc.setHeight(contentView.height * ilc.percentHeight / 100, !isNaN(ilc.percentHeight));
                }
                if (ilc)
                {
    				if (obj.valign == "top")
                        ilc.setY(obj.marginTop + padding.top);
    				else if (valign == "bottom")
                        ilc.setY(maxHeight - child.height - obj.marginBottom);
    				else // TODO: aharui - baseline
                        ilc.setY((maxHeight - child.height) / 2);
                }
                else
                {
                    if (obj.valign == "top")
                        child.y = obj.marginTop + padding.top;
                    else if (valign == "bottom")
                        child.y = maxHeight - child.height - obj.marginBottom;
                    else // TODO: aharui - baseline
                        child.y = (maxHeight - child.height) / 2;
                }
			}

			// Only return true if the contentView needs to be larger; that new
			// size is stored in the model.
			var sizeChanged:Boolean = true;

			host.dispatchEvent( new Event("layoutComplete") );

			return sizeChanged;
		}

        private function setPositionAndHeight(child:IUIBase, top:Number, mt:Number, pt:Number,
                                             bottom:Number, mb:Number, pb:Number, h:Number):void
        {
            var heightSet:Boolean = false;

            var hh:Number = h;
            var ilc:ILayoutChild = child as ILayoutChild;
            if (!isNaN(top))
            {
                if (ilc)
                    ilc.setY(top + mt);
                else
                    child.y = top + mt;
                hh -= top + mt;
            }
            else
            {
                if (ilc)
                    ilc.setY(mt + pt);
                else
                    child.y = mt + pt;
                hh -= mt + pt;
            }
            if (!isNaN(bottom))
            {
                if (!isNaN(top))
                {
                    if (ilc)
                        ilc.setHeight(hh - bottom - mb, true);
                    else
                    {
                        child.height = hh - bottom - mb;
                        heightSet = true;
                    }
                }
                else
                {
                    if (ilc)
                        ilc.setY(h - bottom - mb - child.height);
                    else
                        child.y = h - bottom - mb - child.height;
                }
            }
            if (ilc)
            {
                if (!isNaN(ilc.percentHeight))
                    ilc.setHeight(h * ilc.percentHeight / 100, true);
            }
            if (!heightSet)
                child.dispatchEvent(new Event("sizeChanged"));
        }
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.IBeadModel;
	import org.apache.flex.core.ILayoutChild;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParentIUIBase;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.geom.Rectangle;
	import org.apache.flex.utils.dbg.DOMPathUtil;
    import org.apache.flex.utils.CSSUtils;
    import org.apache.flex.utils.CSSContainerUtils;

    /**
     *  The HorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and vertical-align styles.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class HorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function HorizontalLayout()
		{
		}

        // the strand/host container is also an ILayoutChild because
        // can have its size dictated by the host's parent which is
        // important to know for layout optimization
		private var host:ILayoutChild;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			host = value as ILayoutChild;
		}

        /**
         * @copy org.apache.flex.core.IBeadLayout#layout
         */
		public function layout():Boolean
		{
            //trace(DOMPathUtil.getPath(host), event ? event.type : "fixed size");
			var layoutParent:ILayoutParent = host.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParentIUIBase = layoutParent.contentView;
            var padding:Rectangle = CSSContainerUtils.getPaddingMetrics(host);

			var n:int = contentView.numElements;
            var hostSizedToContent:Boolean = host.isHeightSizedToContent();
            var ilc:ILayoutChild;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			var maxHeight:Number = 0;
            // asking for contentView.height can result in infinite loop if host isn't sized already
            var h:Number = hostSizedToContent ? 0 : contentView.height;
            var w:Number = contentView.width;
			var verticalMargins:Array = [];

			for (var i:int = 0; i < n; i++)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				if (child == null || !child.visible) continue;
                var top:Number = ValuesManager.valuesImpl.getValue(child, "top");
                var bottom:Number = ValuesManager.valuesImpl.getValue(child, "bottom");
                margin = ValuesManager.valuesImpl.getValue(child, "margin");
                marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
                marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
                marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
                marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
                var ml:Number = CSSUtils.getLeftValue(marginLeft, margin, w);
                var mr:Number = CSSUtils.getRightValue(marginRight, margin, w);
                var mt:Number = CSSUtils.getTopValue(marginTop, margin, h);
                var mb:Number = CSSUtils.getBottomValue(marginBottom, margin, h);

                ilc = child as ILayoutChild;
				var lastmr:Number;
				if (marginLeft == "auto")
					ml = 0;
				if (marginRight == "auto")
					mr = 0;
                var xx:Number;
                if (i == 0)
                {
                    if (ilc)
                        ilc.setX(ml + padding.left);
                    else
                        child.x = ml + padding.left;
                }
                else
                {
                    if (ilc)
                        ilc.setX(xx + ml + lastmr);
                    else
                        child.x = xx + ml + lastmr;
                }
                if (ilc)
                {
                    if (!isNaN(ilc.percentWidth))
                        ilc.setWidth(contentView.width * ilc.percentWidth / 100, !isNaN(ilc.percentHeight));
                }
                lastmr = mr;
                var marginObject:Object = {};
                verticalMargins[i] = marginObject;
                var valign:* = ValuesManager.valuesImpl.getValue(child, "vertical-align");
                marginObject.valign = valign;
                if (!hostSizedToContent)
                {
                    // if host is sized by parent,
                    // we can position and size children horizontally now
                    setPositionAndHeight(child, top, mt, padding.top, bottom, mb, padding.bottom, h, valign);
                    maxHeight = Math.max(maxHeight, mt + child.height + mb);
                }
                else
                {
                    if (!isNaN(top))
                    {
                        mt = top;
                        marginObject.top = mt;
                    }
                    if (!isNaN(bottom))
                    {
                        mb = bottom;
                        marginObject.bottom = mb;
                    }
                    maxHeight = Math.max(maxHeight, mt + child.height + mb);
                }
				xx = child.x + child.width;
			}
            if (hostSizedToContent)
            {
                ILayoutChild(contentView).setHeight(maxHeight, true);
                if (host.isWidthSizedToContent())
                    ILayoutChild(contentView).setWidth(xx, true);
                for (i = 0; i < n; i++)
                {
                    child = contentView.getElementAt(i) as IUIBase;
                    if (child == null || !child.visible) continue;
                    var obj:Object = verticalMargins[i];
                    setPositionAndHeight(child, obj.top, obj.marginTop, padding.top,
                        obj.bottom, obj.marginBottom, padding.bottom, maxHeight, obj.valign);
                }
            }

			// Only return true if the contentView needs to be larger; that new
			// size is stored in the model.
			var sizeChanged:Boolean = true;

			host.dispatchEvent( new Event("layoutComplete") );

			return sizeChanged;
		}

        private function setPositionAndHeight(child:IUIBase, top:Number, mt:Number, pt:Number,
                                             bottom:Number, mb:Number, pb:Number, h:Number,
                                             valign:*):void
        {
            var heightSet:Boolean = false;

            var hh:Number = h;
            var ilc:ILayoutChild = child as ILayoutChild;
            if (ilc)
            {
                if (!isNaN(ilc.percentHeight))
                    ilc.setHeight(h * ilc.percentHeight / 100, true);
            }
            if (valign == "top")
            {
                if (!isNaN(top))
                {
                    if (ilc)
                        ilc.setY(top + mt);
                    else
                        child.y = top + mt;
                    hh -= top + mt;
                }
                else
                {
                    if (ilc)
                        ilc.setY(mt + pt);
                    else
                        child.y = mt + pt;
                    hh -= mt + pt;
                }
                if (ilc.isHeightSizedToContent())
                {
                    if (!isNaN(bottom))
                    {
                        if (!isNaN(top))
                        {
                            if (ilc)
                                ilc.setHeight(hh - bottom - mb, true);
                            else
                            {
                                child.height = hh - bottom - mb;
                                heightSet = true;
                            }
                        }
                    }
                }
            }
            else if (valign == "bottom")
            {
                if (!isNaN(bottom))
                {
                    if (ilc)
                        ilc.setY(h - bottom - mb - child.height);
                    else
                        child.y = h - bottom - mb - child.height;
                }
                else
                {
                    if (ilc)
                        ilc.setY(h - mb - child.height);
                    else
                        child.y = h - mb - child.height;
                }
            }
            else
                child.y = (h - child.height) / 2;
            if (!heightSet)
                child.dispatchEvent(new Event("sizeChanged"));
        }
	}
}

fix vertical align

fix vertical align


package aerys.minko.scene.controller
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	use namespace minko_math;

	/**
	 * The TransformController handles the batched update of all the local to world matrices
	 * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
	 * automatically disable itself on other nodes.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class TransformController extends AbstractController
	{
		private var _target					: ISceneNode;

		private var _invalidList			: Boolean;

		private var _nodeToId				: Dictionary;
		private var _idToNode				: Vector.<ISceneNode>
		private var _transforms				: Vector.<Matrix4x4>;
		private var _localToWorldTransforms : Vector.<Matrix4x4>;
		private var _numChildren			: Vector.<uint>;
		private var _firstChildId			: Vector.<uint>
		private var _parentId				: Vector.<int>

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function renderingBeginHandler(scene			: Scene,
										  viewport		: Viewport,
										  destination	: BitmapData,
										  time			: Number) : void
		{
			if (_invalidList)
				updateTransformsList();

			if (_transforms.length)
			{
				var rootTransform 	: Matrix4x4	= _transforms[0];
				var isDirty			: Boolean	= rootTransform._hasChanged;

				if (isDirty)
				{
					_localToWorldTransforms[0].copyFrom(rootTransform);
					rootTransform._hasChanged = false;
				}

				updateLocalToWorld();
			}
		}

		private function updateLocalToWorld(nodeId : uint = 0) : void
		{
			var numNodes 		: uint 	= _transforms.length;
			var childrenOffset	: uint	= 1;

			for (; nodeId < numNodes; ++nodeId)
			{
				var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
				var numChildren		: uint		= _numChildren[nodeId];
				var isDirty			: Boolean	= localToWorld._hasChanged;
				var firstChildId	: uint		= _firstChildId[nodeId];
				var lastChildId		: uint		= firstChildId + numChildren;

				localToWorld._hasChanged = false;

				for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
				{
					var childTransform		: Matrix4x4		= _transforms[childId];
					var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
					var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged;

					if (childIsDirty)
					{
						var child	: ISceneNode	= _idToNode[childId];

						childLocalToWorld
							.copyFrom(childTransform)
							.append(localToWorld);

						childTransform._hasChanged = false;

						child.localToWorldTransformChanged.execute(child, childLocalToWorld);
					}
				}
			}
		}

		private function updateAncestorsAndSelfLocalToWorld(nodeId : int) : void
		{
			var dirtyRoot : int = nodeId;

			while (nodeId >= 0)
			{
				if ((_transforms[nodeId] as Matrix4x4)._hasChanged)
					dirtyRoot = nodeId;

				nodeId = _parentId[nodeId];
			}

			if (dirtyRoot >= 0)
				updateLocalToWorld(dirtyRoot);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											target	: ISceneNode) : void
		{
			if (_target)
				throw new Error('The TransformController cannot have more than one target.');

			_target = target;
			_invalidList = true;

			if (target is Scene)
			{
				(target as Scene).renderingBegin.add(renderingBeginHandler);
				return;
			}

			if (target is Group)
			{
				var targetGroup : Group = target as Group;

				targetGroup.descendantAdded.add(descendantAddedHandler);
				targetGroup.descendantRemoved.add(descendantRemovedHandler);
			}

			target.added.add(addedHandler);
		}

		private function targetRemovedHandler(ctrl		: TransformController,
											  target	: ISceneNode) : void
		{
			target.added.remove(addedHandler);

			if (target is Group)
			{
				var targetGroup : Group = target as Group;

				targetGroup.descendantAdded.remove(descendantAddedHandler);
				targetGroup.descendantRemoved.remove(descendantRemovedHandler);
			}

			_invalidList = false;
			_target = null;

			_nodeToId = null;
			_transforms = null;
			_localToWorldTransforms = null;
			_numChildren = null;
			_idToNode = null;
			_parentId = null;
		}

		private function addedHandler(target : ISceneNode, ancestor : Group) : void
		{
			// the controller will remove itself from the node when it's not its own root anymore
			// but it will watch for the 'removed' signal to add itself back if the node becomes
			// its own root again
			_target.removed.add(removedHandler);
			_target.removeController(this);
		}

		private function removedHandler(target : ISceneNode, ancestor : Group) : void
		{
			if (target.root == target)
			{
				target.removed.remove(removedHandler);
				target.addController(this);
			}
		}

		private function descendantAddedHandler(root		: Group,
												descendant	: ISceneNode) : void
		{
			_invalidList = true;
		}

		private function descendantRemovedHandler(root			: Group,
												  descendant	: ISceneNode) : void
		{
			_invalidList = true;
		}

		private function updateTransformsList() : void
		{
			var root 	: ISceneNode 			= _target.root;
			var nodes 	: Vector.<ISceneNode> 	= new <ISceneNode>[root];
			var nodeId 	: uint 					= 0;

			_nodeToId = new Dictionary(true);
			_transforms = new <Matrix4x4>[];
			_localToWorldTransforms = new <Matrix4x4>[];
			_numChildren = new <uint>[];
			_firstChildId = new <uint>[];
			_idToNode = new <ISceneNode>[];
			_parentId = new <int>[-1];

			while (nodes.length)
			{
				var node 	: ISceneNode 	= nodes.shift();
				var group 	: Group 		= node as Group;

				_nodeToId[node] = nodeId;
				_idToNode[nodeId] = node;
				_transforms[nodeId] = node.transform;
				_localToWorldTransforms[nodeId] = new Matrix4x4().lock();

				if (group)
				{
					var numChildren 	: uint = group.numChildren;
					var firstChildId 	: uint = nodeId + nodes.length + 1;

					_numChildren[nodeId] = numChildren;
					_firstChildId[nodeId] = firstChildId;
					for (var childId : uint = 0; childId < numChildren; ++childId)
					{
						_parentId[uint(firstChildId + childId)] = nodeId;
						nodes.push(group.getChildAt(childId));
					}
				}
				else
				{
					_numChildren[nodeId] = 0;
					_firstChildId[nodeId] = 0;
				}

				++nodeId;
			}

			_invalidList = false;
		}

		public function getLocalToWorldTransform(node 			: ISceneNode,
												 forceUpdate 	: Boolean = false) : Matrix4x4
		{
			if (_invalidList || _nodeToId[node] == undefined)
				updateTransformsList();

			var nodeId : uint = _nodeToId[node];

			if (forceUpdate)
				updateAncestorsAndSelfLocalToWorld(nodeId);

			return _localToWorldTransforms[nodeId];
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	use namespace minko_math;

	/**
	 * The TransformController handles the batched update of all the local to world matrices
	 * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
	 * automatically disable itself on other nodes.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class TransformController extends AbstractController
	{
		private var _target					: ISceneNode;

		private var _invalidList			: Boolean;

		private var _nodeToId				: Dictionary;
		private var _idToNode				: Vector.<ISceneNode>
		private var _transforms				: Vector.<Matrix4x4>;
		private var _localToWorldTransforms : Vector.<Matrix4x4>;
		private var _numChildren			: Vector.<uint>;
		private var _firstChildId			: Vector.<uint>
		private var _parentId				: Vector.<int>

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function renderingBeginHandler(scene			: Scene,
										  viewport		: Viewport,
										  destination	: BitmapData,
										  time			: Number) : void
		{
			if (_invalidList)
				updateTransformsList();

			if (_transforms.length)
			{
				var rootTransform 	: Matrix4x4	= _transforms[0];
				var isDirty			: Boolean	= rootTransform._hasChanged;

				if (isDirty)
				{
					_localToWorldTransforms[0].copyFrom(rootTransform);
					rootTransform._hasChanged = false;
				}

				updateLocalToWorld();
			}
		}

		private function updateLocalToWorld(nodeId : uint = 0) : void
		{
			var numNodes 			: uint 			= _transforms.length;
			var childrenOffset		: uint			= 1;
			var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
			var rootTransform		: Matrix4x4		= _transforms[nodeId];
			var root				: ISceneNode	= _idToNode[childId];

			if (rootTransform._hasChanged)
			{
				rootLocalToWorld.copyFrom(rootTransform);

				if (nodeId != 0)
					rootLocalToWorld.append(_transforms[_parentId]);

				root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
			}

			for (; nodeId < numNodes; ++nodeId)
			{
				var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
				var numChildren		: uint		= _numChildren[nodeId];
				var isDirty			: Boolean	= localToWorld._hasChanged;
				var firstChildId	: uint		= _firstChildId[nodeId];
				var lastChildId		: uint		= firstChildId + numChildren;

				localToWorld._hasChanged = false;

				for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
				{
					var childTransform		: Matrix4x4		= _transforms[childId];
					var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
					var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged;

					if (childIsDirty)
					{
						var child	: ISceneNode	= _idToNode[childId];

						childLocalToWorld
							.copyFrom(childTransform)
							.append(localToWorld);

						childTransform._hasChanged = false;

						child.localToWorldTransformChanged.execute(child, childLocalToWorld);
					}
				}
			}
		}

		private function updateAncestorsAndSelfLocalToWorld(nodeId : int) : void
		{
			var dirtyRoot : int = nodeId;

			while (nodeId >= 0)
			{
				if ((_transforms[nodeId] as Matrix4x4)._hasChanged)
					dirtyRoot = nodeId;

				nodeId = _parentId[nodeId];
			}

			if (dirtyRoot >= 0)
				updateLocalToWorld(dirtyRoot);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											target	: ISceneNode) : void
		{
			if (_target)
				throw new Error('The TransformController cannot have more than one target.');

			_target = target;
			_invalidList = true;

			if (target is Scene)
			{
				(target as Scene).renderingBegin.add(renderingBeginHandler);
				return;
			}

			if (target is Group)
			{
				var targetGroup : Group = target as Group;

				targetGroup.descendantAdded.add(descendantAddedHandler);
				targetGroup.descendantRemoved.add(descendantRemovedHandler);
			}

			target.added.add(addedHandler);
		}

		private function targetRemovedHandler(ctrl		: TransformController,
											  target	: ISceneNode) : void
		{
			target.added.remove(addedHandler);

			if (target is Group)
			{
				var targetGroup : Group = target as Group;

				targetGroup.descendantAdded.remove(descendantAddedHandler);
				targetGroup.descendantRemoved.remove(descendantRemovedHandler);
			}

			_invalidList = false;
			_target = null;

			_nodeToId = null;
			_transforms = null;
			_localToWorldTransforms = null;
			_numChildren = null;
			_idToNode = null;
			_parentId = null;
		}

		private function addedHandler(target : ISceneNode, ancestor : Group) : void
		{
			// the controller will remove itself from the node when it's not its own root anymore
			// but it will watch for the 'removed' signal to add itself back if the node becomes
			// its own root again
			_target.removed.add(removedHandler);
			_target.removeController(this);
		}

		private function removedHandler(target : ISceneNode, ancestor : Group) : void
		{
			if (target.root == target)
			{
				target.removed.remove(removedHandler);
				target.addController(this);
			}
		}

		private function descendantAddedHandler(root		: Group,
												descendant	: ISceneNode) : void
		{
			_invalidList = true;
		}

		private function descendantRemovedHandler(root			: Group,
												  descendant	: ISceneNode) : void
		{
			_invalidList = true;
		}

		private function updateTransformsList() : void
		{
			var root 	: ISceneNode 			= _target.root;
			var nodes 	: Vector.<ISceneNode> 	= new <ISceneNode>[root];
			var nodeId 	: uint 					= 0;

			_nodeToId = new Dictionary(true);
			_transforms = new <Matrix4x4>[];
			_localToWorldTransforms = new <Matrix4x4>[];
			_numChildren = new <uint>[];
			_firstChildId = new <uint>[];
			_idToNode = new <ISceneNode>[];
			_parentId = new <int>[-1];

			while (nodes.length)
			{
				var node 	: ISceneNode 	= nodes.shift();
				var group 	: Group 		= node as Group;

				_nodeToId[node] = nodeId;
				_idToNode[nodeId] = node;
				_transforms[nodeId] = node.transform;
				_localToWorldTransforms[nodeId] = new Matrix4x4().lock();

				if (group)
				{
					var numChildren 	: uint = group.numChildren;
					var firstChildId 	: uint = nodeId + nodes.length + 1;

					_numChildren[nodeId] = numChildren;
					_firstChildId[nodeId] = firstChildId;
					for (var childId : uint = 0; childId < numChildren; ++childId)
					{
						_parentId[uint(firstChildId + childId)] = nodeId;
						nodes.push(group.getChildAt(childId));
					}
				}
				else
				{
					_numChildren[nodeId] = 0;
					_firstChildId[nodeId] = 0;
				}

				++nodeId;
			}

			_invalidList = false;
		}

		public function getLocalToWorldTransform(node 			: ISceneNode,
												 forceUpdate 	: Boolean = false) : Matrix4x4
		{
			if (_invalidList || _nodeToId[node] == undefined)
				updateTransformsList();

			var nodeId : uint = _nodeToId[node];

			if (forceUpdate)
				updateAncestorsAndSelfLocalToWorld(nodeId);

			return _localToWorldTransforms[nodeId];
		}
	}
}

Fix update of a tree root transform matrix.


package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.1";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.0";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

update version 3.9.0

update version 3.9.0