/*****************************************************
*
* Copyright 2009 Adobe Systems Incorporated. All Rights Reserved.
*
*****************************************************
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
*
* The Initial Developer of the Original Code is Adobe Systems Incorporated.
* Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems
* Incorporated. All Rights Reserved.
*
*****************************************************/
package org.osmf.net.httpstreaming.f4f
{
import __AS3__.vec.Vector;
import flash.errors.IllegalOperationError;
import flash.events.EventDispatcher;
import flash.utils.ByteArray;
CONFIG::LOGGING
{
import org.osmf.logging.Logger;
import org.osmf.logging.Log;
}
[ExcludeClass]
/**
* @private
*
* The parser that takes byte array and converts into a list of boxes in sequence.
*/
internal class BoxParser extends EventDispatcher
{
/**
* Constructor
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function BoxParser()
{
super();
_ba = null;
}
/**
* Set bytes to be parsed upon.
*
* @param ba The byte array to be parsed upon.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function init(ba:ByteArray):void
{
_ba = ba;
_ba.position = 0;
}
/**
* Read a 4 byte unsigned integer and a 4 byte string from the byte array, construct
* and return a BoxInfo object
*
* @return a BoxInfo object
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function getNextBoxInfo():BoxInfo
{
if (_ba == null || _ba.bytesAvailable < F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH)
{
return null;
}
var size:Number = _ba.readUnsignedInt();
var type:String = _ba.readUTFBytes(F4FConstants.FIELD_TYPE_LENGTH);
return new BoxInfo(size, type);
}
/**
* Parse and return a list of root level boxes in the order
* of the boxes in the byte array.
*
* @return the list of root level boxes.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function getBoxes():Vector.<Box>
{
var boxes:Vector.<Box> = new Vector.<Box>();
var bi:BoxInfo = getNextBoxInfo();
while (bi != null)
{
if (bi.type == F4FConstants.BOX_TYPE_ABST)
{
var abst:AdobeBootstrapBox = new AdobeBootstrapBox();
parseAdobeBootstrapBox(bi, abst);
boxes.push(abst);
}
else if (bi.type == F4FConstants.BOX_TYPE_AFRA)
{
var afra:AdobeFragmentRandomAccessBox = new AdobeFragmentRandomAccessBox();
parseAdobeFragmentRandomAccessBox(bi, afra);
boxes.push(afra);
}
else if (bi.type == F4FConstants.BOX_TYPE_MDAT)
{
var mdat:MediaDataBox = new MediaDataBox();
parseMediaDataBox(bi, mdat);
boxes.push(mdat);
}
else
{
_ba.position = _ba.position + bi.size - (F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH);
}
bi = getNextBoxInfo();
if (bi != null && bi.size <= 0)
{
break;
}
}
return boxes;
}
/**
* Parse and returns AFRA
*
* @return AFRA if the current byte array from the current position is indeed an AFRA, null otherwise.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function readFragmentRandomAccessBox(bi:BoxInfo):AdobeFragmentRandomAccessBox
{
var afra:AdobeFragmentRandomAccessBox = new AdobeFragmentRandomAccessBox();
parseAdobeFragmentRandomAccessBox(bi, afra);
return afra;
}
/**
* Parse and returns ABST
*
* @return ABST if the current byte array from the current position is indeed an ABST, null otherwise.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function readAdobeBootstrapBox(bi:BoxInfo):AdobeBootstrapBox
{
var abst:AdobeBootstrapBox = new AdobeBootstrapBox();
this.parseAdobeBootstrapBox(bi, abst);
return abst;
}
// Internals
//
/**
* Reads 8 bytes from the byte array, treats the 8 bytes as an long integer and pack the values
* into a Number. This is a little tricky since ActionScript does not have a long integer type.
* The solution is to read an unsigned integer which is 4 bytes long, times the unsigned integer
* by 2^32, and assign the value to a Number. Next, read the next unsigned integer, and then
* add the new unsigned integer to the existing number.
*
* @return the number that is created from the 8 bytes read from the byte array at the current position.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
internal function readLongUIntToNumber():Number
{
if (_ba == null || _ba.bytesAvailable < 8)
{
throw new IllegalOperationError("not enough length for readLongUIntToNumer");
}
var result:Number = _ba.readUnsignedInt();
result *= 4294967296.0;
result += _ba.readUnsignedInt();
return result;
}
/**
* Reads and returns an unsigned integer from the byte array at the current position.
*
* @return an unsigned integer.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
private function readUnsignedInt():uint
{
if (_ba == null || _ba.bytesAvailable < 4)
{
throw new IllegalOperationError("not enough length for readUnsignedInt");
}
return _ba.readUnsignedInt();
}
/**
* Reads the number of data bytes, specified by the length parameter, from the byte stream.
* The bytes are read into the ByteArray object specified by the bytes parameter, and the
* bytes are written into the destination ByteArray starting at the position specified by offset.
*
* @param bytes The ByteArray object to read data into.
* @param offset The offset (position) in bytes at which the read data should be written.
* @param length The number of bytes to read. The default value of 0 causes all available data to be read.
*
* @throws IllegalOperaitonError There is not sufficient data available to read.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
private function readBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void
{
if (_ba == null || _ba.bytesAvailable < length)
{
throw new IllegalOperationError("not enough length for readBytes: " + length);
}
return _ba.readBytes(bytes, offset, length);
}
/**
* Reads an unsigned byte from the byte stream. The returned value is in the range 0 to 255.
*
* @return A 32-bit unsigned integer between 0 and 255.
*
* @throws IllegalOperaitonError There is not sufficient data available to read.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
private function readUnsignedByte():uint
{
if (_ba == null || _ba.bytesAvailable < 1)
{
throw new IllegalOperationError("not enough length for readUnsingedByte");
}
return _ba.readUnsignedByte();
}
/**
* Read a number of bytes, treat them as bytes of an unsigned integer and return the uint.
* The number of bytes must be between 0 and 4.
*
* @return A 32-bit unsigned integer converted from the list of bytes.
*
* @throws IllegalOperaitonError There is not sufficient data available to read.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
private function readBytesToUint(length:uint):uint
{
if (_ba == null || _ba.bytesAvailable < length)
{
throw new IllegalOperationError("not enough length for readUnsingedByte");
}
if (length > 4)
{
throw new IllegalOperationError("number of bytes to read must be equal or less than 4");
}
var result:uint = 0;
for (var i:uint = 0; i < length; i++)
{
result = (result << 8);
var byte:uint = _ba.readUnsignedByte();
result += byte;
}
return result;
}
/**
* Read a null terminated string.
*
* @return the null terminated string.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
private function readString():String
{
var pos:uint = _ba.position;
while (_ba.position < _ba.length)
{
var c:uint = _ba.readByte();
if (c == 0)
{
break;
}
}
var length:uint = _ba.position - pos;
_ba.position = pos;
return _ba.readUTFBytes(length);
}
// Internal
//
private function parseBox(boxInfo:BoxInfo, box:Box):void
{
var size:Number = boxInfo.size;
var boxLength:uint = F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH;
if (boxInfo.size == F4FConstants.FLAG_USE_LARGE_SIZE)
{
size = readLongUIntToNumber();
boxLength += F4FConstants.FIELD_LARGE_SIZE_LENGTH;
}
if (boxInfo.type == F4FConstants.EXTENDED_TYPE)
{
// Read past the extended type.
var extendedType:ByteArray = new ByteArray();
readBytes(extendedType, 0, F4FConstants.FIELD_EXTENDED_TYPE_LENGTH);
boxLength += F4FConstants.FIELD_EXTENDED_TYPE_LENGTH;
}
box.size = size;
box.type = boxInfo.type;
box.boxLength = boxLength;
}
private function parseFullBox(boxInfo:BoxInfo, fullBox:FullBox):void
{
parseBox(boxInfo, fullBox);
fullBox.version = readUnsignedByte();
fullBox.flags = readBytesToUint(FULL_BOX_FIELD_FLAGS_LENGTH);
}
private function parseAdobeBootstrapBox(boxInfo:BoxInfo, abst:AdobeBootstrapBox):void
{
parseFullBox(boxInfo, abst);
abst.bootstrapVersion = readUnsignedInt();
var temp:uint = readUnsignedByte();
abst.profile = (temp >> 6);
abst.live = ((temp & 0x20) == 0x20);
abst.update = ((temp & 0x1) == 0x1);
abst.timeScale = readUnsignedInt();
abst.currentMediaTime = readLongUIntToNumber();
abst.smpteTimeCodeOffset = readLongUIntToNumber();
abst.movieIdentifier = readString();
var serverEntryCount:uint = readUnsignedByte();
var serverBaseURLs:Vector.<String> = new Vector.<String>();
for (var i:int = 0; i < serverEntryCount; i++)
{
serverBaseURLs.push(readString());
}
abst.serverBaseURLs = serverBaseURLs;
var qualityEntryCount:uint = readUnsignedByte();
var qualitySegmentURLModifiers:Vector.<String> = new Vector.<String>();
for (i = 0; i < qualityEntryCount; i++)
{
qualitySegmentURLModifiers.push(readString());
}
abst.qualitySegmentURLModifiers = qualitySegmentURLModifiers;
abst.drmData = readString();
abst.metadata = readString();
var segmentRunTableCount:uint = readUnsignedByte();
var segmentRunTables:Vector.<AdobeSegmentRunTable> = new Vector.<AdobeSegmentRunTable>();
for (i = 0; i < segmentRunTableCount; i++)
{
boxInfo = getNextBoxInfo();
if (boxInfo.type == F4FConstants.BOX_TYPE_ASRT)
{
var asrt:AdobeSegmentRunTable = new AdobeSegmentRunTable();
parseAdobeSegmentRunTable(boxInfo, asrt);
segmentRunTables.push(asrt);
}
else
{
throw new IllegalOperationError("Unexpected data structure: " + boxInfo.type);
}
}
abst.segmentRunTables = segmentRunTables;
var fragmentRunTableCount:uint = readUnsignedByte();
var fragmentRunTables:Vector.<AdobeFragmentRunTable> = new Vector.<AdobeFragmentRunTable>();
for (i = 0; i < fragmentRunTableCount; i++)
{
boxInfo = getNextBoxInfo();
if (boxInfo.type == F4FConstants.BOX_TYPE_AFRT)
{
var afrt:AdobeFragmentRunTable = new AdobeFragmentRunTable();
parseAdobeFragmentRunTable(boxInfo, afrt);
fragmentRunTables.push(afrt);
}
else
{
throw new IllegalOperationError("Unexpected data structure: " + boxInfo.type);
}
}
abst.fragmentRunTables = fragmentRunTables;
}
private function parseAdobeSegmentRunTable(boxInfo:BoxInfo, asrt:AdobeSegmentRunTable):void
{
parseFullBox(boxInfo, asrt);
var qualityEntryCount:uint = readUnsignedByte();
var qualitySegmentURLModifiers:Vector.<String> = new Vector.<String>();
for (var i:uint = 0; i < qualityEntryCount; i++)
{
qualitySegmentURLModifiers.push(readString());
}
asrt.qualitySegmentURLModifiers = qualitySegmentURLModifiers;
var entryCount:uint = readUnsignedInt();
for (i = 0; i < entryCount; i++)
{
asrt.addSegmentFragmentPair(new SegmentFragmentPair(readUnsignedInt(), readUnsignedInt()));
}
}
private function parseAdobeFragmentRunTable(boxInfo:BoxInfo, afrt:AdobeFragmentRunTable):void
{
parseFullBox(boxInfo, afrt);
afrt.timeScale = readUnsignedInt();
var qualityEntryCount:uint = readUnsignedByte();
var qualitySegmentURLModifiers:Vector.<String> = new Vector.<String>();
for (var i:uint = 0; i < qualityEntryCount; i++)
{
qualitySegmentURLModifiers.push(readString());
}
afrt.qualitySegmentURLModifiers = qualitySegmentURLModifiers;
var entryCount:uint = readUnsignedInt();
for (i = 0; i < entryCount; i++)
{
var fdp:FragmentDurationPair = new FragmentDurationPair();
parseFragmentDurationPair(fdp);
afrt.addFragmentDurationPair(fdp);
}
}
private function parseFragmentDurationPair(fdp:FragmentDurationPair):void
{
fdp.firstFragment = readUnsignedInt();
fdp.durationAccrued = readLongUIntToNumber();
fdp.duration = readUnsignedInt();
if (fdp.duration == 0)
{
fdp.discontinuityIndicator = readUnsignedByte();
}
CONFIG::LOGGING
{
logger.debug(" firstFragment=" + fdp.firstFragment +
" duration=" + fdp.duration +
" durationAccrued=" + fdp.durationAccrued +
" discontinuityIndicator=" + fdp.discontinuityIndicator);
}
}
private function parseAdobeFragmentRandomAccessBox(boxInfo:BoxInfo, afra:AdobeFragmentRandomAccessBox):void
{
parseFullBox(boxInfo, afra);
var sizes:uint = readBytesToUint(1);
var longIdFields:Boolean = ((sizes & AFRA_MASK_LONG_ID) > 0);
var longOffsetFields:Boolean = ((sizes & AFRA_MASK_LONG_OFFSET) > 0);
var globalEntries:Boolean = ((sizes & AFRA_MASK_GLOBAL_ENTRIES) > 0);
afra.timeScale = readUnsignedInt();
CONFIG::LOGGING
{
logger.debug("------------------- AFRA -------------------");
logger.debug(" timeScale=" + afra.timeScale);
}
var entryCount:uint = readUnsignedInt();
var localRandomAccessEntries:Vector.<LocalRandomAccessEntry> = new Vector.<LocalRandomAccessEntry>();
for (var i:uint = 0; i < entryCount; i++)
{
var lrae:LocalRandomAccessEntry = new LocalRandomAccessEntry();
parseLocalRandomAccessEntry(lrae, longOffsetFields);
localRandomAccessEntries.push(lrae);
}
afra.localRandomAccessEntries = localRandomAccessEntries;
var globalRandomAccessEntries:Vector.<GlobalRandomAccessEntry> = new Vector.<GlobalRandomAccessEntry>();
if (globalEntries)
{
entryCount = readUnsignedInt();
for (i = 0; i < entryCount; i++)
{
var grae:GlobalRandomAccessEntry = new GlobalRandomAccessEntry();
parseGlobalRandomAccessEntry(grae, longIdFields, longOffsetFields);
globalRandomAccessEntries.push(grae);
}
}
afra.globalRandomAccessEntries = globalRandomAccessEntries;
}
private function parseLocalRandomAccessEntry(lrae:LocalRandomAccessEntry, longOffsetFields:Boolean):void
{
lrae.time = readLongUIntToNumber();
if (longOffsetFields)
{
lrae.offset = readLongUIntToNumber();
}
else
{
lrae.offset = readUnsignedInt();
}
CONFIG::LOGGING
{
logger.debug(" time=" + lrae.time + " offset=" + lrae.offset);
}
}
private function parseGlobalRandomAccessEntry(grae:GlobalRandomAccessEntry, longIdFields:Boolean, longOffsetFields:Boolean):void
{
grae.time = readLongUIntToNumber();
if (longIdFields)
{
grae.segment = readUnsignedInt();
grae.fragment = readUnsignedInt();
}
else
{
grae.segment = readBytesToUint(2);
grae.fragment = readBytesToUint(2);
}
if (longOffsetFields)
{
grae.afraOffset = readLongUIntToNumber();
grae.offsetFromAfra = readLongUIntToNumber();
}
else
{
grae.afraOffset = readUnsignedInt();
grae.offsetFromAfra = readUnsignedInt();
}
CONFIG::LOGGING
{
logger.debug(
" time=" + grae.time +
" segment=" + grae.segment +
" fragment=" + grae.fragment +
" afraOffset=" + grae.afraOffset +
" offsetFromAfra=" + grae.offsetFromAfra);
}
}
private function parseMediaDataBox(boxInfo:BoxInfo, mdat:MediaDataBox):void
{
parseBox(boxInfo, mdat);
var data:ByteArray = new ByteArray();
readBytes(data, 0, mdat.size - mdat.boxLength);
mdat.data = data;
}
private var _ba:ByteArray;
private static const FULL_BOX_FIELD_FLAGS_LENGTH:uint = 3;
private static const AFRA_MASK_LONG_ID:uint = 128;
private static const AFRA_MASK_LONG_OFFSET:uint = 64;
private static const AFRA_MASK_GLOBAL_ENTRIES:uint = 32;
CONFIG::LOGGING
{
private static const logger:Logger = Log.getLogger("org.osmf.net.httpstreaming.f4f.BoxParser");;
}
}
}