public MatroskaFile(string path)
        {
            this._path = path;
            this._stream = new FastFileStream(path);

            // read header
            Element headerElement = this.ReadElement();
            if (headerElement != null && headerElement.Id == ElementId.Ebml)
            {
                // read segment
                this._stream.Seek(headerElement.DataSize, SeekOrigin.Current);
                this._segmentElement = this.ReadElement();
                if (this._segmentElement != null && this._segmentElement.Id == ElementId.Segment)
                {
                    this._valid = true; // matroska file must start with ebml header and segment
                }
            }
        }
        /// <summary>
        /// The read subtitle block.
        /// </summary>
        /// <param name="blockElement">
        /// The block element.
        /// </param>
        /// <param name="clusterTimeCode">
        /// The cluster time code.
        /// </param>
        /// <returns>
        /// The <see cref="MatroskaSubtitle"/>.
        /// </returns>
        private MatroskaSubtitle ReadSubtitleBlock(Element blockElement, long clusterTimeCode)
        {
            int trackNumber = (int)this.ReadVariableLengthUInt();
            if (trackNumber != this._subtitleRipTrackNumber)
            {
                this._stream.Seek(blockElement.EndPosition, SeekOrigin.Begin);
                return null;
            }

            short timeCode = this.ReadInt16();

            // lacing
            byte flags = (byte)this._stream.ReadByte();
            int frames;
            switch (flags & 6)
            {
                case 0: // 00000000 = No lacing
                    Debug.Print("No lacing");
                    break;
                case 2: // 00000010 = Xiph lacing
                    frames = this._stream.ReadByte() + 1;
                    Debug.Print("Xiph lacing ({0} frames)", frames);
                    break;
                case 4: // 00000100 = Fixed-size lacing
                    frames = this._stream.ReadByte() + 1;
                    for (int i = 0; i < frames; i++)
                    {
                        this._stream.ReadByte(); // frames
                    }

                    Debug.Print("Fixed-size lacing ({0} frames)", frames);
                    break;
                case 6: // 00000110 = EMBL lacing
                    frames = this._stream.ReadByte() + 1;
                    Debug.Print("EBML lacing ({0} frames)", frames);
                    break;
            }

            // save subtitle data
            int dataLength = (int)(blockElement.EndPosition - this._stream.Position);
            byte[] data = new byte[dataLength];
            this._stream.Read(data, 0, dataLength);

            return new MatroskaSubtitle(data, clusterTimeCode + timeCode);
        }
        /// <summary>
        /// The read block group element.
        /// </summary>
        /// <param name="clusterElement">
        /// The cluster element.
        /// </param>
        /// <param name="clusterTimeCode">
        /// The cluster time code.
        /// </param>
        private void ReadBlockGroupElement(Element clusterElement, long clusterTimeCode)
        {
            MatroskaSubtitle subtitle = null;

            Element element;
            while (this._stream.Position < clusterElement.EndPosition && (element = this.ReadElement()) != null)
            {
                switch (element.Id)
                {
                    case ElementId.Block:
                        subtitle = this.ReadSubtitleBlock(element, clusterTimeCode);
                        if (subtitle == null)
                        {
                            return;
                        }

                        this._subtitleRip.Add(subtitle);
                        break;
                    case ElementId.BlockDuration:
                        long duration = (long)this.ReadUInt((int)element.DataSize);
                        if (subtitle != null)
                        {
                            subtitle.Duration = duration;
                        }

                        break;
                    default:
                        this._stream.Seek(element.DataSize, SeekOrigin.Current);
                        break;
                }
            }
        }
        /// <summary>
        /// The read cluster.
        /// </summary>
        /// <param name="clusterElement">
        /// The cluster element.
        /// </param>
        private void ReadCluster(Element clusterElement)
        {
            long clusterTimeCode = 0;

            Element element;
            while (this._stream.Position < clusterElement.EndPosition && (element = this.ReadElement()) != null)
            {
                switch (element.Id)
                {
                    case ElementId.Timecode:
                        clusterTimeCode = (long)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.BlockGroup:
                        this.ReadBlockGroupElement(element, clusterTimeCode);
                        break;
                    case ElementId.SimpleBlock:
                        MatroskaSubtitle subtitle = this.ReadSubtitleBlock(element, clusterTimeCode);
                        if (subtitle != null)
                        {
                            this._subtitleRip.Add(subtitle);
                        }

                        break;
                    default:
                        this._stream.Seek(element.DataSize, SeekOrigin.Current);
                        break;
                }
            }
        }
        /// <summary>
        /// The read tracks element.
        /// </summary>
        /// <param name="tracksElement">
        /// The tracks element.
        /// </param>
        private void ReadTracksElement(Element tracksElement)
        {
            this._tracks = new List<MatroskaTrackInfo>();

            Element element;
            while (this._stream.Position < tracksElement.EndPosition && (element = this.ReadElement()) != null)
            {
                if (element.Id == ElementId.TrackEntry)
                {
                    this.ReadTrackEntryElement(element);
                }
                else
                {
                    this._stream.Seek(element.DataSize, SeekOrigin.Current);
                }
            }
        }
        /// <summary>
        /// The read info element.
        /// </summary>
        /// <param name="infoElement">
        /// The info element.
        /// </param>
        private void ReadInfoElement(Element infoElement)
        {
            Element element;
            while (this._stream.Position < infoElement.EndPosition && (element = this.ReadElement()) != null)
            {
                switch (element.Id)
                {
                    case ElementId.TimecodeScale:

                        // Timestamp scale in nanoseconds (1.000.000 means all timestamps in the segment are expressed in milliseconds)
                        this._timecodeScale = (int)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.Duration:

                        // Duration of the segment (based on TimecodeScale)
                        this._duration = element.DataSize == 4 ? this.ReadFloat32() : this.ReadFloat64();
                        this._duration /= this._timecodeScale * 1000000.0;
                        break;
                    default:
                        this._stream.Seek(element.DataSize, SeekOrigin.Current);
                        break;
                }
            }
        }
        /// <summary>
        /// The read content encoding element.
        /// </summary>
        /// <param name="contentEncodingElement">
        /// The content encoding element.
        /// </param>
        /// <param name="contentCompressionAlgorithm">
        /// The content compression algorithm.
        /// </param>
        /// <param name="contentEncodingType">
        /// The content encoding type.
        /// </param>
        private void ReadContentEncodingElement(Element contentEncodingElement, ref int contentCompressionAlgorithm, ref int contentEncodingType)
        {
            Element element;
            while (this._stream.Position < contentEncodingElement.EndPosition && (element = this.ReadElement()) != null)
            {
                switch (element.Id)
                {
                    case ElementId.ContentEncodingOrder:
                        ulong contentEncodingOrder = this.ReadUInt((int)element.DataSize);
                        Debug.WriteLine("ContentEncodingOrder: " + contentEncodingOrder);
                        break;
                    case ElementId.ContentEncodingScope:
                        ulong contentEncodingScope = this.ReadUInt((int)element.DataSize);
                        Debug.WriteLine("ContentEncodingScope: " + contentEncodingScope);
                        break;
                    case ElementId.ContentEncodingType:
                        contentEncodingType = (int)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.ContentCompression:
                        Element compElement;
                        while (this._stream.Position < element.EndPosition && (compElement = this.ReadElement()) != null)
                        {
                            switch (compElement.Id)
                            {
                                case ElementId.ContentCompAlgo:
                                    contentCompressionAlgorithm = (int)this.ReadUInt((int)compElement.DataSize);
                                    break;
                                case ElementId.ContentCompSettings:
                                    ulong contentCompSettings = this.ReadUInt((int)compElement.DataSize);
                                    Debug.WriteLine("ContentCompSettings: " + contentCompSettings);
                                    break;
                                default:
                                    this._stream.Seek(element.DataSize, SeekOrigin.Current);
                                    break;
                            }
                        }

                        break;
                    default:
                        this._stream.Seek(element.DataSize, SeekOrigin.Current);
                        break;
                }
            }
        }
        /// <summary>
        /// The read track entry element.
        /// </summary>
        /// <param name="trackEntryElement">
        /// The track entry element.
        /// </param>
        private void ReadTrackEntryElement(Element trackEntryElement)
        {
            long defaultDuration = 0;
            bool isVideo = false;
            bool isAudio = false;
            bool isSubtitle = false;
            int trackNumber = 0;
            string name = string.Empty;
            string language = string.Empty;
            string codecId = string.Empty;
            string codecPrivate = string.Empty;

            // var biCompression = string.Empty;
            int contentCompressionAlgorithm = -1;
            int contentEncodingType = -1;

            Element element;
            while (this._stream.Position < trackEntryElement.EndPosition && (element = this.ReadElement()) != null)
            {
                switch (element.Id)
                {
                    case ElementId.DefaultDuration:
                        defaultDuration = (int)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.Video:
                        this.ReadVideoElement(element);
                        isVideo = true;
                        break;
                    case ElementId.Audio:
                        isAudio = true;
                        break;
                    case ElementId.TrackNumber:
                        trackNumber = (int)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.Name:
                        name = this.ReadString((int)element.DataSize, Encoding.UTF8);
                        break;
                    case ElementId.Language:
                        language = this.ReadString((int)element.DataSize, Encoding.ASCII);
                        break;
                    case ElementId.CodecId:
                        codecId = this.ReadString((int)element.DataSize, Encoding.ASCII);
                        break;
                    case ElementId.TrackType:
                        switch (this._stream.ReadByte())
                        {
                            case 1:
                                isVideo = true;
                                break;
                            case 2:
                                isAudio = true;
                                break;
                            case 17:
                                isSubtitle = true;
                                break;
                        }

                        break;
                    case ElementId.CodecPrivate:
                        codecPrivate = this.ReadString((int)element.DataSize, Encoding.UTF8);

                        // if (codecPrivate.Length > 20)
                        // biCompression = codecPrivate.Substring(16, 4);
                        break;
                    case ElementId.ContentEncodings:
                        contentCompressionAlgorithm = 0; // default value
                        contentEncodingType = 0; // default value

                        Element contentEncodingElement = this.ReadElement();
                        if (contentEncodingElement != null && contentEncodingElement.Id == ElementId.ContentEncoding)
                        {
                            this.ReadContentEncodingElement(element, ref contentCompressionAlgorithm, ref contentEncodingType);
                        }

                        break;
                }

                this._stream.Seek(element.EndPosition, SeekOrigin.Begin);
            }

            this._tracks.Add(new MatroskaTrackInfo { TrackNumber = trackNumber, IsVideo = isVideo, IsAudio = isAudio, IsSubtitle = isSubtitle, Language = language, CodecId = codecId, CodecPrivate = codecPrivate, Name = name, ContentEncodingType = contentEncodingType, ContentCompressionAlgorithm = contentCompressionAlgorithm });

            if (isVideo)
            {
                if (defaultDuration > 0)
                {
                    this._frameRate = 1.0 / (defaultDuration / 1000000000.0);
                }

                this._videoCodecId = codecId;
            }
        }
 /// <summary>
 /// The read video element.
 /// </summary>
 /// <param name="videoElement">
 /// The video element.
 /// </param>
 private void ReadVideoElement(Element videoElement)
 {
     Element element;
     while (this._stream.Position < videoElement.EndPosition && (element = this.ReadElement()) != null)
     {
         switch (element.Id)
         {
             case ElementId.PixelWidth:
                 this._pixelWidth = (int)this.ReadUInt((int)element.DataSize);
                 break;
             case ElementId.PixelHeight:
                 this._pixelHeight = (int)this.ReadUInt((int)element.DataSize);
                 break;
             default:
                 this._stream.Seek(element.DataSize, SeekOrigin.Current);
                 break;
         }
     }
 }
        /// <summary>
        /// The find track start in cluster.
        /// </summary>
        /// <param name="cluster">
        /// The cluster.
        /// </param>
        /// <param name="targetTrackNumber">
        /// The target track number.
        /// </param>
        /// <returns>
        /// The <see cref="long"/>.
        /// </returns>
        private long FindTrackStartInCluster(Element cluster, int targetTrackNumber)
        {
            long clusterTimeCode = 0L;
            long trackStartTime = -1L;
            bool done = false;

            Element element;
            while (this._stream.Position < cluster.EndPosition && (element = this.ReadElement()) != null && !done)
            {
                switch (element.Id)
                {
                    case ElementId.None:
                        done = true;
                        break;
                    case ElementId.Timecode:

                        // Absolute timestamp of the cluster (based on TimecodeScale)
                        clusterTimeCode = (long)this.ReadUInt((int)element.DataSize);
                        break;
                    case ElementId.BlockGroup:
                        this.ReadBlockGroupElement(element, clusterTimeCode);
                        break;
                    case ElementId.SimpleBlock:
                        int trackNumber = (int)this.ReadVariableLengthUInt();
                        if (trackNumber == targetTrackNumber)
                        {
                            // Timecode (relative to Cluster timecode, signed int16)
                            trackStartTime = this.ReadInt16();
                            done = true;
                        }

                        break;
                }

                this._stream.Seek(element.EndPosition, SeekOrigin.Begin);
            }

            return (clusterTimeCode + trackStartTime) * this._timecodeScale / 1000000;
        }