FileDocCategorySizeDatePackage
QuicktimeParser.javaAPI DocJMF 2.1.1e124914Mon May 12 12:20:52 BST 2003com.sun.media.parser.video

QuicktimeParser

public class QuicktimeParser extends BasicPullParser

Fields Summary
private final boolean
enableHintTrackSupport
private static ContentDescriptor[]
supportedFormat
private PullSourceStream
stream
private Track[]
tracks
private Seekable
seekableStream
private boolean
mdatAtomPresent
private boolean
moovAtomPresent
public static final int
MVHD_ATOM_SIZE
public static final int
TKHD_ATOM_SIZE
public static final int
MDHD_ATOM_SIZE
public static final int
MIN_HDLR_ATOM_SIZE
public static final int
MIN_STSD_ATOM_SIZE
public static final int
MIN_STTS_ATOM_SIZE
public static final int
MIN_STSC_ATOM_SIZE
public static final int
MIN_STSZ_ATOM_SIZE
public static final int
MIN_STCO_ATOM_SIZE
public static final int
MIN_STSS_ATOM_SIZE
public static final int
MIN_VIDEO_SAMPLE_DATA_SIZE
public static final int
MIN_AUDIO_SAMPLE_DATA_SIZE
public static final int
TRACK_ENABLED
public static final int
TRACK_IN_MOVIE
public static final int
TRACK_IN_PREVIEW
public static final int
TRACK_IN_POSTER
public static final String
VIDEO
public static final String
AUDIO
public static final String
HINT
private static final int
DATA_SELF_REFERENCE_FLAG
private static final int
HINT_NOP_IGNORE
private static final int
HINT_IMMEDIATE_DATA
private static final int
HINT_SAMPLE_DATA
private static final int
HINT_SAMPLE_DESCRIPTION
private MovieHeader
movieHeader
private int
numTracks
private int
numSupportedTracks
private int
numberOfHintTracks
private static int
MAX_TRACKS_SUPPORTED
private TrakList[]
trakList
private TrakList
currentTrack
private int
keyFrameTrack
private SettableTime
mediaTime
private int
hintAudioTrackNum
private boolean
debug
private boolean
debug1
private boolean
debug2
private Object
seekSync
private int
tmpIntBufferSize
private byte[]
tmpBuffer
Constructors Summary
Methods Summary
private javax.media.protocol.CachedStreamgetCacheStream()

	return cacheStream;
    
public javax.media.TimegetDuration()

	return movieHeader.duration;
    
public javax.media.TimegetMediaTime()

	return null;  // TODO
    
public java.lang.StringgetName()
Returns a descriptive name for the plug-in. This is a user readable string.

	return "Parser for quicktime file format";
    
public javax.media.protocol.ContentDescriptor[]getSupportedInputContentDescriptors()

	return supportedFormat;
    
public javax.media.Track[]getTracks()

 	if (tracks != null)
 	    return tracks;
	
	if (seekableStream == null) {
	    return new Track[0];
	}

	if (cacheStream != null) {
	    // Disable jitter buffer during parsing of the header
	    cacheStream.setEnabledBuffering(false);
	}
	readHeader();
	if (cacheStream != null) {
	    cacheStream.setEnabledBuffering(true);
	}

	tracks = new Track[numSupportedTracks];

	// System.out.println("numTracks is " + numTracks);
	// System.out.println("numSupportedTracks is " + numSupportedTracks);
	int index = 0;
	for (int i = 0; i < numSupportedTracks; i++) {
	    TrakList trakInfo = trakList[i];
	    if (trakInfo.trackType.equals(AUDIO)) {
		tracks[i] = new AudioTrack(trakInfo);
// 		System.out.println("track id " + (index-1) + " : " +
// 				   tracks[index-1]);
	    } else if (trakInfo.trackType.equals(VIDEO)) {
		tracks[i] = new VideoTrack(trakInfo);
// 		System.out.println("track id " + (index-1) + " : " +
// 				   tracks[index-1]);
	    }
	}
	for (int i = 0; i < numSupportedTracks; i++) {
	    TrakList trakInfo = trakList[i];
	    if (trakInfo.trackType.equals(HINT)) {
		int trackBeingHinted = trakInfo.trackIdOfTrackBeingHinted;
		for (int j = 0; j < numTracks; j++) {
		    if (trackBeingHinted == trakList[j].id) {
			trakInfo.indexOfTrackBeingHinted = j;
			String hintedTrackType = trakList[j].trackType;
			String encodingOfHintedTrack = 
                           trakList[trakInfo.indexOfTrackBeingHinted].media.encoding;

			if (encodingOfHintedTrack.equals("agsm"))
			    encodingOfHintedTrack = "gsm";

			String rtpEncoding = encodingOfHintedTrack + "/rtp";
			if (hintedTrackType.equals(AUDIO)) {
			    int channels;
			    String encoding;
			    int frameSizeInBytes;
			    int samplesPerBlock;
			    int sampleRate;
			    Audio audio = (Audio) (trakList[j].media);

			    hintAudioTrackNum = i;

			    channels =  audio.channels;
			    frameSizeInBytes =  audio.frameSizeInBits / 8;
			    samplesPerBlock = audio.samplesPerBlock;
			    sampleRate = audio.sampleRate;

			    ((Hint) trakInfo.media).format =
				new AudioFormat(rtpEncoding,
						(double) sampleRate,
						8, // sampleSizeInBits [$$$ hardcoded]
						channels);

  			    tracks[i] = new HintAudioTrack(trakInfo,
  								 channels,
								 rtpEncoding,
  								 frameSizeInBytes,
  								 samplesPerBlock,
  								 sampleRate);
// 			    System.out.println("track id " + (index-1) + " : " +
// 					       tracks[index-1]);

			} else if (hintedTrackType.equals(VIDEO)) {

			    int indexOfTrackBeingHinted = trakInfo.indexOfTrackBeingHinted;
			    TrakList sampleTrakInfo = null;
			    if (indexOfTrackBeingHinted >= 0) {
				sampleTrakInfo = trakList[indexOfTrackBeingHinted];
			    }

			    int width = 0;
			    int height = 0;
			    if (sampleTrakInfo != null) {
				Video sampleTrakVideo = (Video) sampleTrakInfo.media;
				width = sampleTrakVideo.width;
				height = sampleTrakVideo.height;
			    }

			    if ( (width > 0) && (height > 0) ) {
				((Hint) trakInfo.media).format =
				    new VideoFormat(rtpEncoding,
						new Dimension(width, height),
						Format.NOT_SPECIFIED,
						null, Format.NOT_SPECIFIED);

// 				System.out.println("VIDEO HINT TRACK FORMAT is " +
// 						   ((Hint) trakInfo.media).format);
			    }
			    HintVideoTrack hintVideoTrack = 
				new HintVideoTrack(trakInfo);
                            tracks[i] = hintVideoTrack;

// 			    System.out.println("track id " + (index-1) + " : " +
// 					       tracks[index-1]);
			}
			break;
		    }
		}
	    }
	}
	return tracks;
    
private booleanisSupported(java.lang.String trackType)

	if (enableHintTrackSupport) {
	    return ( trackType.equals(VIDEO) ||
		     trackType.equals(AUDIO) ||
		     trackType.equals(HINT)
		     );
	} else {
	    return ( trackType.equals(VIDEO) ||
		     trackType.equals(AUDIO)
		     );
	}
    
private booleanparseAtom()

	boolean readSizeField = false;

	try {
	    int atomSize = readInt(stream);
// 	    System.out.println("atomSize is " + atomSize);
	    readSizeField = true;
	    
	    String atom = readString(stream);
// 	    System.out.println("atom is " + atom);
	    
	    if ( atomSize < 8 )
		throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);

	    if ( atom.equals("moov") )
		return parseMOOV(atomSize - 8);
	    if ( atom.equals("mdat") )
		return parseMDAT(atomSize - 8);
	    skipAtom(atom + " [not implemented]", atomSize - 8);
	    return true;
	} catch (IOException e) {
	    // System.err.println("parseAtom: IOException " + e);
	    if (!readSizeField) {
		// System.out.println("EOM");
		return false; // EOM. Parsing done
	    }
	    throw new BadHeaderException("Unexpected End of Media");
	}
    
private com.sun.media.parser.video.QuicktimeParser$AudioparseAudioSampleData(java.lang.String encoding, int dataSize)

	skip(stream, 2); // data reference index
	// TODO: check for  dataSize >= MIN_AUDIO_SAMPLE_DATA_SIZE

	/**
	 * Skip versiom(2), Revision Level (2), Vendor(4),
	 */
	skip(stream, 8);

	Audio audio = new Audio();
	audio.encoding = encoding;
	audio.channels = readShort(stream);
	audio.bitsPerSample = readShort(stream);

	/**
	 * Skip compression id (2),
	 * Skip packset size (2),
	 */
	skip(stream, 4);
	int sampleRate = readInt(stream);
	/**
	 * The media timeScale (foound in the mdhd atom) seems to
	 * represent the sampleRate (because it represents units/sec)
	 * This sampleRate field for some reason contains the
	 * timeScale shifted left by 16 bits. In other words sampleRate
	 * is media timeScale times 65536.
	 * Instead of dividing this by 65536, I am just using the
	 * media timeScale as sampleRate
	 * CHECK
	 */
	// audio.sampleRate = sampleRate >> 16; // Also works
	audio.sampleRate = currentTrack.mediaTimeScale;
	// System.out.println("mediaTimeScale is " + currentTrack.mediaTimeScale);

	skip(stream, dataSize -2 -MIN_AUDIO_SAMPLE_DATA_SIZE); // 2 for data ref. index
	return audio;
    
private voidparseCTAB(int ctabSize)

 // TODO
	try {
	    // System.out.println("ctab not handled yet");
	    skip(stream, ctabSize); // DUMMY 
	} catch (IOException e) {
	    //TODO
	    throw new BadHeaderException("....");
	}
    
private voidparseDINF(int dinfSize)

	try {
	    int remainingSize = dinfSize;

	    // System.out.println("dinfSize is " + dinfSize);
	    while (remainingSize > 0) {
		int atomSize = readInt(stream);
		String atom = readString(stream);
		// System.out.println("dinf: atomSize is " + atomSize);
		// System.out.println("dinf: atom is " + atom);
		if (atom.equals("dref")) {
		    parseDREF(atomSize - 8);
		} else {
		    skipAtom(atom + " [Unknown atom in dinf]", atomSize - 8);
		}
		remainingSize -= atomSize;
	    }
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past DIMF atom");
	}
    
private voidparseDREF(int drefSize)

	try {
	    // TODO: add size check
// 	    if (drefSize < MIN_DREF_ATOM_SIZE) {
// 		throw new BadHeaderException("dref atom: header size is incorrect");
// 	    }

	    skip(stream, 4); // skip version and flags
	    int numEntries = readInt(stream);
	    // System.out.println("dref: number of entries is " + numEntries);

	    for (int i = 0; i < numEntries; i++) {
		int drefEntrySize = readInt(stream);
		// System.out.println("drefEntrySize is " + drefSize);
		int type = readInt(stream);
		// System.out.println("dref entry type is " + type);
		/**
		 * Version: A 1-byte specification of the version of
		 * these data references.
		 * Flags: A 3-byte space for data reference flags.
		 * There is one defined flag. Self reference This flag
		 * indicates that the media's data is in the same file
		 * as the movie atom. On the Macintosh, and other file
		 * systems with multifork files, set this flag to 1
		 * even if the data resides in a different fork
		 * from the movie atom. This flag's value is 0x0001.
		 */
		int versionPlusFlag = readInt(stream);
		// System.out.println("versionPlusFlag is " + versionPlusFlag);
		skip(stream, drefEntrySize -(4+4+4));
		if ( (versionPlusFlag & DATA_SELF_REFERENCE_FLAG) <= 0 ) {
		    throw new BadHeaderException("Only self contained Quicktime movies are supported");
		}
	    }
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past DREF atom");
	}
    
private voidparseHDLR(int hdlrSize)
moov/trak/mdia/hdlr

	try {
	    if (hdlrSize < MIN_HDLR_ATOM_SIZE) {
		throw new BadHeaderException("hdlr atom: header size is incorrect");
	    }
	    
	    // Skip version(1), flags(3), component type(4)
	    skip(stream, 8);
	    currentTrack.trackType = readString(stream);
	    // System.out.println("track type is " + currentTrack.trackType);
	    currentTrack.supported = isSupported(currentTrack.trackType);

	    // Skip the rest of the fields including the variable component
	    // name field
	    skip(stream, hdlrSize -8 -4);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past HDLR atom");
	}
    
private com.sun.media.parser.video.QuicktimeParser$HintparseHintSampleData(java.lang.String encoding, int dataSize)


	// TODO: check for  dataSize >= MIN_HINT_SAMPLE_DATA_SIZE

	if (!encoding.equals("rtp ")) {
	    System.err.println("Hint track Data Format is not rtp");
	}
	// System.out.println("parseHintSampleData: dataSize is " + dataSize);


	Hint hint = new Hint();

	int dataReferenceIndex = readShort(stream);
	int hintTrackVersion = readShort(stream);

 	if (hintTrackVersion == 0) {
 	    System.err.println("Hint Track version #0 is not supported");
 	    System.err.println("Use QuickTimePro to convert it to version #1");
	    currentTrack.supported = false;
	    if ((dataSize - 2 - 2) > 0)
		skip(stream, (dataSize -2 -2));
	    return hint;
	}

	int lastCompatibleHintTrackVersion = readShort(stream);


	int maxPacketSize = readInt(stream);
	currentTrack.maxPacketSize = maxPacketSize;
	int remaining = dataSize -2 -2 -2 -4;

	if (debug1) {
	    System.out.println("dataReferenceIndex is " + dataReferenceIndex);
	    System.out.println("hintTrackVersion is " + hintTrackVersion);
	    System.out.println("lastCompatibleHintTrackVersion is " + lastCompatibleHintTrackVersion);
	    System.out.println("maxPacketSize is " + maxPacketSize);
	    System.out.println("remaining is " + remaining);
	}

	while (remaining > 8) {
	    // Additional data is present;

	    int entryLength = readInt(stream);
	    remaining -= 4;
	    if ( entryLength > 8) {
		if (debug2)
		    System.out.println("entryLength is " + entryLength);
		// entryLength -= 4;
		String dataTag = readString(stream);
		if (debug2)
		    System.out.println("dataTag is " + dataTag);
		remaining -= 4;
		// entryLength -= 4;
		// TODO: assuming that the data tag is 'tims'. It can be tsro,snro,rely
		if (dataTag.equals("tims")) {
		    // 32-bit integer specifying the RTP timescale. This entry is
		    // required for RTP data.
		    int rtpTimeScale = readInt(stream);
		    // System.out.println("  rtpTimeScale is " + rtpTimeScale);
		    // currentTrack.rtpTimeScale = dataValue;
		    // entryLength -= 4;
		    remaining -= 4;
		} else if (dataTag.equals("tsro")) {
		    // 32-bit integer specifying the offset to add to the stored
		    // timestamp when sending RTP packets. If this entry is not
		    // present, a random offset should be used, as specified by the
		    // IETF. If this entry is 0, use an offset of 0 (no offset).

		    System.out.println("QuicktimeParser: rtp: tsro dataTag not supported");
		    int rtpTimeStampOffset = readInt(stream);
		    remaining -= 4;
		} else if (dataTag.equals("snro")) {
		    // 32-bit integer specifying the offset to add to the sequence
		    // number when sending RTP packets. If this entry is not present, a
		    // random offset should be used, as specified by the IETF. If this
		    // entry is 0, use an offset of 0 (no offset).

		    System.out.println("QuicktimeParser: rtp: snro dataTag not supported");
		    int rtpSequenceNumberOffset = readInt(stream);
		    // System.out.println("rtpSequenceNumberOffset is " + rtpSequenceNumberOffset);
		    remaining -= 4;
		} else if (dataTag.equals("rely")) {
		    // 8-bit flag indicating whether this track should or must be sent
		    // over a reliable transport, such as TCP/IP. If this entry is not
		    // present, unreliable transport should be used, such as RTP/UDP.
		    // The current client software for QuickTime streaming will only
		    // receive streaming tracks sent using RTP/UDP.

		    System.out.println("QuicktimeParser: rtp: rely dataTag not supported");
		    int rtpReliableTransportFlag = readByte(stream);
		    // System.out.println("rtpReliableTransportFlag is " + rtpReliableTransportFlag);
		    remaining--;
		} else {
		    // Unknown flag: Error.
		    // TODO: handle this without skipping if possible
		    // May not be possible because we don't know how many bytes
		    // to skip before the next tag.
		    skip(stream, remaining);
		    remaining = 0;
		}
	    } else {
		skip(stream, remaining);
		remaining = 0;
		break;
	    }
	}
	if (remaining > 0)
	    skip(stream, remaining);
	return hint;
    
private booleanparseMDAT(int size)

	try {
	    mdatAtomPresent = true;
	    movieHeader.mdatStart = getLocation( stream ); // Need this ??? TODO
	    movieHeader.mdatSize = size; // Need this ??? TODO
	    /** Seek past the MDAT atom only if the MOOV atom
	     *  hasn't been seen yet. 
	     *  The only reason to seek past the MDAT atom even if the
	     *  MOOV atom has been seen is to handle top level atoms
	     *  like PNOT (Movie Preview data). We currently don't support
	     *  PNOT atom.
	     *  Also, We don't know how fast the
	     *  seek is. If it is based on RandomAccessFile like
	     *  Sun's file datasource, then it is pretty fast.
	     *  But if it a cached http datasource over a slow
	     *  internet connection, then the seek will take a long
	     *  time. So seeking past the MDAT atom is not done unless
	     *  the MOOV atom hasn't been seen yet.
	     */
	    if (!moovAtomPresent) {
		skip(stream, size);
		return true; // Parsing continues as MOOV atom hasn't been seen
	    }
	    return false; // Parsing done
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past MDAT atom");
	}
    
private voidparseMDHD(int mdhdSize)
moov/trak/mdia/mdhd

	try {
	    if (mdhdSize != MDHD_ATOM_SIZE) {
		throw new BadHeaderException("mdhd atom: header size is incorrect");
	    }
	    
	    // Skip version(1), flags(3), creation time(4), modification time(4)
	    skip(stream, 12);
	    int timeScale = readInt(stream);
	    int duration = readInt(stream);
	    currentTrack.mediaDuration = new Time((double) duration / timeScale);
	    currentTrack.mediaTimeScale = timeScale;
	    skip(stream, 4); // Skip language(2) and quality(2) fields
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past MDHD atom");
	}
    
private booleanparseMDIA(int mdiaSize)
Required atoms are mdhd, hdlr and minf Doesn't say in the spec. that hdlr or minf is a required atom, but can we play a qt file without a hdlr or minf atoms hdlr atom should come before minf atom Return true if the trackType is supported moov/trak/mdia

	boolean hdlrAtomPresent = false;
	boolean minfAtomPresent = false;;

	try {
	    currentTrack.trackType = null;
	    int remainingSize = mdiaSize;
	    int atomSize = readInt(stream);
	    String atom = readString(stream);

	    if ( atomSize < 8 )
		throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
	    
	    if ( ! atom.equals("mdhd") ) {
		throw new BadHeaderException("Expected mdhd atom but got " + atom);
	    }
	    parseMDHD(atomSize - 8);
	    remainingSize -= atomSize;
	    // TODO: before calling parseXXX, should check if
	    // (atomSize - 8) >= remainingSize
	    while (remainingSize > 0) {
		atomSize = readInt(stream);
		atom = readString(stream);
		if (atom.equals("hdlr")) {
		    parseHDLR(atomSize - 8); // Updates trackType in currentTrack
		    hdlrAtomPresent = true;
		} else if (atom.equals("minf")) {
		    if (currentTrack.trackType == null) {
			throw new BadHeaderException("In MDIA atom container minf atom appears before hdlr");
		    }
		    if (currentTrack.supported) {
			parseMINF(atomSize - 8);
		    } else {
			skipAtom(atom + " [atom in mdia] as trackType " +
				 currentTrack.trackType + " is not supported",
				 atomSize - 8);
		    }
		    minfAtomPresent = true;
		} else {
		    skipAtom(atom + " [atom in mdia: not implemented]", atomSize - 8);
		}
		remainingSize -= atomSize;
	    }
	    if (!hdlrAtomPresent)
		throw new BadHeaderException("hdlr atom not present in mdia atom container");
	    if (!minfAtomPresent)
		throw new BadHeaderException("minf atom not present in mdia atom container");

	    return (currentTrack.supported);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past MDIA atom");
	}
    
private voidparseMINF(int minfSize)
Required atoms are [vsg]mhd, hdlr Optional atoms are dinf and stbl Currently we skip [vsg]mhd, hdlr and dinf atoms and only handle stbl


	boolean hdlrAtomPresent = false;
	try {
	    int remainingSize = minfSize;
	    int atomSize = readInt(stream);
	    String atom = readString(stream);

	    if ( atomSize < 8 )
		throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
	    
	    if ( ! atom.endsWith("hd") ) {
		throw new BadHeaderException("Expected media information header atom but got " + atom);
	    }
	    skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);

	    remainingSize -= atomSize;
	    // TODO: before calling parseXXX, should check if
	    // (atomSize - 8) >= remainingSize
	    while (remainingSize > 0) {
		atomSize = readInt(stream);
		atom = readString(stream);
		if (atom.equals("hdlr")) {
		    skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);
		    hdlrAtomPresent = true;
		} else if (atom.equals("dinf")) {
		    parseDINF(atomSize - 8);
		} else if (atom.equals("stbl")) {
		    parseSTBL(atomSize - 8);
		} else {
		    skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);
		}
		remainingSize -= atomSize;
	    }
	    if (!hdlrAtomPresent)
		throw new BadHeaderException("hdlr atom not present in minf atom container");

	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past MINF atom");
	}
    
private booleanparseMOOV(int moovSize)
Required atoms are mvhd and trak Doesn't say in the spec. that trak is a required atom, but can we play a qt file without a trak atom??

	boolean trakAtomPresent = false;

	try {
	    moovAtomPresent = true;
	    long moovMax = getLocation(stream) + moovSize;
	    int remainingSize = moovSize;


	    int atomSize = readInt(stream);
	    String atom = readString(stream);

	    if ( atomSize < 8 )
		throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);

	    if ( ! atom.equals("mvhd") ) {
		if (atom.equals("cmov"))
		    throw new BadHeaderException("Compressed movie headers are not supported");
		else
		    throw new BadHeaderException("Expected mvhd atom but got " + atom);
	    }
	    parseMVHD(atomSize - 8);
// 	    System.out.println("Duration of movie is " +
// 			       movieHeader.duration.getSeconds());
	    remainingSize -= atomSize;

	    // TODO: before calling parseXXX, should check if
	    // (atomSize - 8) >= remainingSize
	    while (remainingSize > 0) {
		atomSize = readInt(stream);
		atom = readString(stream);
		if (atom.equals("trak")) {
		    if (trakList[numSupportedTracks] == null) {
			trakList[numSupportedTracks] = currentTrack = new TrakList();
		    }
		    if (parseTRAK(atomSize - 8)) {
			numSupportedTracks++;
		    }
		    trakAtomPresent = true;
		    numTracks++;
		} else if (atom.equals("ctab")) {
		    parseCTAB(atomSize - 8);
		} else {
		    skipAtom(atom + " [atom in moov: not implemented]", atomSize - 8);
		}
               remainingSize -= atomSize;
	    }

	    if (!trakAtomPresent)
		throw new BadHeaderException("trak atom not present in trak atom container");
	    // Parsing is done if the MDAT atom has also been seen.
	    return !mdatAtomPresent;
	} catch (IOException e) {
	    throw new BadHeaderException("IOException when parsing the header");
	}
    
private voidparseMVHD(int size)
MVHD is a leaf atom of size MVHD_ATOM_SIZE (100)

	try {
	    if (size != MVHD_ATOM_SIZE) {
		throw new BadHeaderException("mvhd atom: header size is incorrect");
	    }

	    // Skip version(1), flags(3), create time (4), mod time (4)
	    skip(stream, 12);

	    movieHeader.timeScale = readInt(stream);
	    int duration = readInt(stream);
	    movieHeader.duration = new Time((double) duration /
					    movieHeader.timeScale);
	    int preferredRate = readInt(stream);
	    int preferredVolume = readShort(stream);
	    
	    skip(stream, 10); // Reserved 
	    skip(stream, 36); // MATRIX
	    
	    int previewTime = readInt(stream);
	    int previewDuration = readInt(stream);
	    int posterTime = readInt(stream);
	    int selectionTime = readInt(stream);
	    int selectionDuration = readInt(stream);
	    int currentTime = readInt(stream);
	    int nextTrackID = readInt(stream);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past MVHD atom");
	}
    
private voidparseSTBL(int stblSize)
Required atoms are none.

	try {
	    int remainingSize = stblSize;

	    while (remainingSize > 0) {
		int atomSize = readInt(stream);
		String atom = readString(stream);
		if (atom.equals("stsd")) {
		    parseSTSD(atomSize - 8);
		} else if (atom.equals("stts")) {
		    parseSTTS(atomSize - 8);
		} else if (atom.equals("stss")) {
		    parseSTSS(atomSize - 8);
		} else if (atom.equals("stsc")) {
		    parseSTSC(atomSize - 8);
		} else if (atom.equals("stsz")) {
		    parseSTSZ(atomSize - 8);
		} else if (atom.equals("stco")) {
		    parseSTCO(atomSize - 8);
		} else if (atom.equals("stsh")) {
		    //		    parseSTSH(atomSize - 8);
		    skipAtom(atom + " [not implemented]", atomSize - 8);
		} else {
		    skipAtom(atom + " [UNKNOWN atom in stbl: ignored]", atomSize - 8);
		}
		remainingSize -= atomSize;
	    }
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STBL atom");
	}
    
private voidparseSTCO(int stcoSize)
Chunk offset atom STCO is a leaf atom of minimum size MIN_STCO_ATOM_SIZE (8)

	if (debug2)
	    System.out.println("rtp:parseSTCO: " + stcoSize);

	try {
	    if (stcoSize < MIN_STCO_ATOM_SIZE) {
		throw new BadHeaderException("stco atom: header size is incorrect");
	    }
	    
	    /**
	     * Skip versiom(1), Flags(3)
	     */
	    skip(stream, 4);
	    // numEntries should be equal to number of Chunks
	    int numEntries = readInt(stream);
	    currentTrack.numberOfChunks = numEntries;
	    int[] chunkOffsets = new int[numEntries];
	    int requiredSize = (stcoSize - MIN_STCO_ATOM_SIZE - numEntries*4);
	    if ( requiredSize < 0) {
		throw new BadHeaderException("stco atom: inconsistent number_of_entries field");
	    }

	    int remaining = numEntries;
	    // 1 int is written in each loop
	    int numIntsWrittenPerLoop = 1;
	    int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
	    int i = 0;
	    while (remaining > 0) {
		int numEntriesPerLoop =
		    (remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
		
		readBytes(stream, tmpBuffer,
			  numEntriesPerLoop * numIntsWrittenPerLoop * 4);
		int offset = 0;
		for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
		    chunkOffsets[i] = parseIntFromArray(tmpBuffer, offset, true);
		    offset += 4;
		}
		remaining -= numEntriesPerLoop;
	    }
	    currentTrack.chunkOffsets = chunkOffsets;
	    skip(stream, requiredSize);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STCO atom");
	}
    
private voidparseSTSC(int stscSize)

	try {
	    if (stscSize < MIN_STSC_ATOM_SIZE) {
		throw new BadHeaderException("stsc atom: header size is incorrect");
	    }
	    
	    /**
	     * Skip versiom(1), Flags(3)
	     */
	    skip(stream, 4);
	    int numEntries = readInt(stream);
	    int requiredSize = (stscSize - MIN_STSC_ATOM_SIZE - numEntries*12);
	    if ( requiredSize < 0) {
		throw new BadHeaderException("stsc atom: inconsistent number_of_entries field");
	    }
	    /**
	     * At this point we don't know how many chunks there are
	     * and so we cannot compute the samplePerChunk array
	     * TODO: make use of the sampleDescriptionId field
	     */
	    int compactSamplesChunkNum[] = new int[numEntries];
	    int compactSamplesPerChunk[] = new int[numEntries];
	    byte[] tmpBuf = new byte[numEntries*4*3];
	    readBytes(stream, tmpBuf, numEntries*4*3);
	    int offset = 0;
	    for (int i = 0; i < numEntries; i++) {
 		compactSamplesChunkNum[i] = parseIntFromArray(tmpBuf, offset, true);
		offset += 4;
 		compactSamplesPerChunk[i] = parseIntFromArray(tmpBuf, offset, true);
		offset += 4;
 		// int sampleDescriptionId = readInt(stream);
		offset += 4; // skip next 4 bytes
	    }
	    tmpBuf = null;
	    currentTrack.compactSamplesChunkNum = compactSamplesChunkNum;
	    currentTrack.compactSamplesPerChunk = compactSamplesPerChunk;
	    skip(stream, requiredSize);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STSC atom");
	}
    
private voidparseSTSD(int stsdSize)
STSD is a leaf atom of minimum size MIN_STSD_ATOM_SIZE (8)

	// System.out.println("stsd size is " + stsdSize);

	try {
	    if (stsdSize < MIN_STSD_ATOM_SIZE) {
		throw new BadHeaderException("stsd atom: header size is incorrect");
	    }

	    // Note: if the trackType is not
	    // supported the minf atom is skipped and so you will not
	    // come here.

	    skip(stream, 4); // skip version and flags

	    int numEntries = readInt(stream);

	    //$$ System.out.println("stsd: numEntries is " + numEntries);
	    if ( numEntries > 1) {
		// System.err.println("Multiple formats in a track not supported");
	    }
	    for (int i = 0; i < numEntries; i++) {
		int sampleDescriptionSize = readInt(stream);
		//$$ System.out.println("stsd: sampleDescriptionSize is " + sampleDescriptionSize);
		// CHECK ?? spec. says int but is it a 4 letter String????
		String encoding = readString(stream);
		// System.out.println("stsd: encoding is " + encoding);

		if (i != 0) {
		    skip(stream, sampleDescriptionSize - 8);
		    continue;
		}

		// skip(stream, 8); // 6 reserved bytes + 2 for data reference index
		skip(stream, 6); // 6 reserved bytes

		// TODO: check of sampleDescriptionSize is atleast 16 bytes
		if (currentTrack.trackType.equals(VIDEO)) {
		    currentTrack.media = 
			parseVideoSampleData(encoding,
					     sampleDescriptionSize -4 -4 -6);
		} else if (currentTrack.trackType.equals(AUDIO)) {
		    currentTrack.media = 
			parseAudioSampleData(encoding,
					     sampleDescriptionSize -4 -4 -6);
		} else if (currentTrack.trackType.equals(HINT)) {
		    numberOfHintTracks++;
		    currentTrack.media = 
			parseHintSampleData(encoding,
					     sampleDescriptionSize -4 -4 -6);
		} else {
		    // Note: you will never come into this else block.
		    // If the trackType is not supported, the minf atom is skipped
		    // and so you will not come here.

		    skip(stream, 
			 sampleDescriptionSize - 4 - 4 -6);
		}
		
	    }

	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STSD atom");
	}
    
private voidparseSTSS(int stssSize)
Sync sample atom STSS is a leaf atom of minimum size MIN_STSS_ATOM_SIZE (8)

	try {
	    if (stssSize < MIN_STSS_ATOM_SIZE) {
		throw new BadHeaderException("stss atom: header size is incorrect");
	    }
	    
	    /**
	     * Skip versiom(1), Flags(3)
	     */
	    skip(stream, 4);
	    int numEntries = readInt(stream);

	    int requiredSize = (stssSize - MIN_STSS_ATOM_SIZE - numEntries*4);
	    if ( requiredSize < 0) {
		throw new BadHeaderException("stss atom: inconsistent number_of_entries field");
	    }

	    if (numEntries < 1) {
		skip(stream, requiredSize);
		return;
	    }

	    int[] syncSamples = new int[numEntries];

	    int remaining = numEntries;
	    // 1 int is written in each loop
	    int numIntsWrittenPerLoop = 1;
	    int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
	    int i = 0;
	    while (remaining > 0) {
		int numEntriesPerLoop =
		    (remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
		
		readBytes(stream, tmpBuffer,
			  numEntriesPerLoop * numIntsWrittenPerLoop * 4);
		int offset = 0;
		for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
		    syncSamples[i] = parseIntFromArray(tmpBuffer, offset, true);
		    offset += 4;
		}
		remaining -= numEntriesPerLoop;
	    }
	    currentTrack.syncSamples = syncSamples;
	    skip(stream, requiredSize);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STSS atom");
	}
    
private voidparseSTSZ(int stszSize)
Sample Size Atom STSZ is a leaf atom of minimum size MIN_STSZ_ATOM_SIZE (8)

	if (debug2)
	    System.out.println("parseSTSZ: " + stszSize);
	try {
	    if (stszSize < MIN_STSZ_ATOM_SIZE) {
		throw new BadHeaderException("stsz atom: header size is incorrect");
	    }
	    
	    /**
	     * Skip versiom(1), Flags(3)
	     */
	    skip(stream, 4);
	    currentTrack.sampleSize = readInt(stream);
	    if (currentTrack.sampleSize != 0) {
		// All samples are of same sample size
		skip(stream, stszSize - MIN_STSZ_ATOM_SIZE);
		currentTrack.media.maxSampleSize = currentTrack.sampleSize;
		return;
            }
	    
	    // All samples are not of same size
	    if ( (stszSize - MIN_STSZ_ATOM_SIZE) < 4) { // for numEntries
		throw new BadHeaderException("stsz atom: incorrect atom size");
            }
	    
	    int numEntries = readInt(stream);
	    if (currentTrack.numberOfSamples == 0) {
		currentTrack.numberOfSamples = numEntries; // TODO: ????
	    } else {
		    // TODO: if not they are inconsistent: should throw BadHeaderException
	    }
	    
	    int requiredSize = (stszSize - MIN_STSZ_ATOM_SIZE 
                                - 4 // for numEntries
                                - numEntries*4);
	    if ( requiredSize < 0) {
		throw new BadHeaderException("stsz atom: inconsistent number_of_entries field");
	    }
	    int[] sampleSizeArray = new int[numEntries];
	    int maxSampleSize = Integer.MIN_VALUE;
	    int value;

	    int remaining = numEntries;
	    // 1 int is written in each loop
	    int numIntsWrittenPerLoop = 1;
	    int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
	    int i = 0;
	    while (remaining > 0) {
		int numEntriesPerLoop =
		    (remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
		
		readBytes(stream, tmpBuffer,
			  numEntriesPerLoop * numIntsWrittenPerLoop * 4);
		int offset = 0;
		for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
		    value = parseIntFromArray(tmpBuffer, offset, true);
		    offset += 4;
		    if (value > maxSampleSize)
			maxSampleSize = value;
		    sampleSizeArray[i] = value;
		}
		remaining -= numEntriesPerLoop;
	    }
	    currentTrack.sampleSizeArray = sampleSizeArray;
	    currentTrack.media.maxSampleSize = maxSampleSize;
	    skip(stream, requiredSize);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STSZ atom");
	}
    
private voidparseSTTS(int sttsSize)
Time to Sample atom STTS is a leaf atom of minimum size MIN_STTS_ATOM_SIZE (8)

	if (debug2)
	    System.out.println("parseSTTS: " + sttsSize);
	try {
	    
	    if (sttsSize < MIN_STTS_ATOM_SIZE) {
		throw new BadHeaderException("stts atom: header size is incorrect");
	    }
	    
	    /**
	     * Skip versiom(1), Flags(3)
	     */
	    skip(stream, 4);
	    int numEntries = readInt(stream);
	    
	    if (debug2)
		System.out.println("numEntries is " + numEntries);
	    int requiredSize = (sttsSize - MIN_STTS_ATOM_SIZE - numEntries*8);
	    if ( requiredSize < 0) {
		throw new BadHeaderException("stts atom: inconsistent number_of_entries field");
	    }
	    int totalNumSamples = 0;

	    double timeScaleFactor = (1.0 / currentTrack.mediaTimeScale);
	    if ( numEntries == 1) {
 		totalNumSamples = readInt(stream);
		currentTrack.durationOfSamples = readInt(stream) * timeScaleFactor;
	    } else {
		int[] timeToSampleIndices = new int[numEntries];
		double[] durations = new double[numEntries];

		timeToSampleIndices[0] = readInt(stream);
		totalNumSamples += timeToSampleIndices[0];
		durations[0] = readInt(stream) * timeScaleFactor *
		    timeToSampleIndices[0];
		                       
		int remaining = numEntries - 1; // As first 2 entries is already read.
		// 2 ints are written in each loop
		int numIntsWrittenPerLoop = 2;
		// integer division
		int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
		int i = 1;
		while (remaining > 0) {
		    int numEntriesPerLoop =
			(remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;

		    readBytes(stream, tmpBuffer,
			      numEntriesPerLoop * numIntsWrittenPerLoop * 4);


		    int offset = 0;
		    for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
			timeToSampleIndices[i] = 
			    parseIntFromArray(tmpBuffer, offset, true);
			offset += 4;
			int value = parseIntFromArray(tmpBuffer, offset, true);
			offset += 4;
			durations[i] += ( value * timeScaleFactor *
					  timeToSampleIndices[i] +
					  durations[i-1] );
			totalNumSamples += timeToSampleIndices[i];
			timeToSampleIndices[i] = totalNumSamples;
		    }
		    remaining -= numEntriesPerLoop;
		}
		currentTrack.timeToSampleIndices = timeToSampleIndices;
		currentTrack.cumulativeDurationOfSamples = durations;
	    }

	    if (currentTrack.numberOfSamples == 0) {
		currentTrack.numberOfSamples = totalNumSamples;
	    } else {
		// TODO: if not they are inconsistent: should throw BadHeaderException
	    }
	    

	    skip(stream, requiredSize);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past STTS atom");
	}
    
private voidparseTKHD(int tkhdSize)
TKHD is a leaf atom of size TKHD_ATOM_SIZE (84)

	try {
	    if (tkhdSize != TKHD_ATOM_SIZE) {
		throw new BadHeaderException("mvhd atom: header size is incorrect");
	    }
	    int iVersionPlusFlag = readInt(stream);
	    currentTrack.flag = iVersionPlusFlag & 0xFFFFFF;
	    skip(stream, 8); // Skip creation time and modification time
	    currentTrack.id = readInt(stream);
// 	    System.out.println("<<<<<<<< id is >>>>>> " + currentTrack.id);
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// 	    System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
	    skip(stream, 4); // Skip reserved field
	    int duration = readInt(stream);
	    currentTrack.duration = new Time((double) duration /
					     movieHeader.timeScale);
	    skip(stream, tkhdSize -4 -8 -4 -4 -4); // Skip the rest of the fields
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past TKHD atom");
	}
    
private booleanparseTRAK(int trakSize)
Required atoms are tkhd and mdia

	boolean mdiaAtomPresent = false;
	boolean supported = false; // is trackType supported
	try {
	    int remainingSize = trakSize;
	    int atomSize = readInt(stream);
	    String atom = readString(stream);
	    
	    if ( atomSize < 8 )
		throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
	    
	    if ( ! atom.equals("tkhd") ) {
		throw new BadHeaderException("Expected tkhd atom but got " + atom);
	    }
	    parseTKHD(atomSize - 8);
	    remainingSize -= atomSize;
	    // TODO: before calling parseXXX, should check if
	    // (atomSize - 8) >= remainingSize
	    while (remainingSize > 0) {
		atomSize = readInt(stream);
		atom = readString(stream);
		if (atom.equals("mdia")) {
		    supported = parseMDIA(atomSize - 8);
		    mdiaAtomPresent = true;
		} else if (atom.equals("tref")) {
		    parseTREF(atomSize - 8);
		} else {
		    skipAtom(atom + " [atom in trak: not implemented]", atomSize - 8);
		}
		remainingSize -= atomSize;
	    }
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past TRAK atom");
	}
	if (!mdiaAtomPresent)
	    throw new BadHeaderException("mdia atom not present in trak atom container");

	// FYI$$: Some files like vinton.mov have an audio track but
	// do not have a stsd chunk. Apple's movie player plays only
	// the video in this case. This case is now handled.
	// gracefully. But there may be other cases like this.
	// We need to update the if statement accordingly.
	if ( supported &&
	     (currentTrack.media == null) ) {
	    supported = false;
	}
	return supported;
    
private voidparseTREF(int size)

	try {

	    int childAtomSize = readInt(stream);
	    size -= 4;
	    // System.out.println("parseTREF: childAtomSize is " + childAtomSize);
	    
	    String atom = readString(stream);
	    size -= 4;
	    
	    if (atom.equalsIgnoreCase("hint")) {
		currentTrack.trackIdOfTrackBeingHinted = readInt(stream);
		size -= 4;
		// System.out.println("trackBeingHinted is " + currentTrack.trackIdOfTrackBeingHinted);
	    }
	    skip(stream, size);
	} catch (IOException e) {
	    throw new BadHeaderException("Got IOException when seeking past HDLR atom");
	}
    
private com.sun.media.parser.video.QuicktimeParser$VideoparseVideoSampleData(java.lang.String encoding, int dataSize)

	// TODO: check for  dataSize >= MIN_VIDEO_SAMPLE_DATA_SIZE
	skip(stream, 2); // data reference index
	/**
	 * Skip versiom(2), Revision Level (2), Vendor(4),
	 * Temporal Quality (4), Spatial Quality (4);
	 */
	skip(stream, 16);

	Video video = new Video();
	video.encoding = encoding;

	video.width = readShort(stream);
	video.height = readShort(stream);
	/**
	 * Skip Horizontal resolution (4),
	 * Skip Vertical resolution (4),
	 * Skip data size (4),
	 * Skip frame count (2),
	 */
	skip(stream, 14);
	/* Skip compressor name */
	skip(stream, 32);
	video.pixelDepth = readShort(stream);
	video.colorTableID = readShort(stream);

	int colorTableSize = 0;
	if (video.colorTableID == 0) {
	    // Color table follows colorTableID
	    colorTableSize = readInt(stream);
	    skip(stream, colorTableSize -4); // TODO: DUMMY
	}

	skip(stream, dataSize - 2 - MIN_VIDEO_SAMPLE_DATA_SIZE -
	           - colorTableSize); // 2 for data ref. index
	return video;
    
private voidreadHeader()


	while ( parseAtom() );
	if ( !moovAtomPresent )
	    throw new BadHeaderException("moov atom not present");
	
	if ( !mdatAtomPresent )
	    throw new BadHeaderException("mdat atom not present");

// 	System.out.println("Number of tracks is " + numTracks);
// 	System.out.println("Number of supported/valid tracks is " + numSupportedTracks);

	for (int i = 0; i < numSupportedTracks; i++) {
	    TrakList trak = trakList[i];

// 	    System.out.println("track index " + i + " encoding " +
// 			       trak.media.encoding);

// 	    System.out.println("Number of frames in track " +
// 			       trak.trackType + " : " +
// 			       + i +
// 			       " is " + trak.numberOfSamples);
// 	    System.out.println("Duration of track " + i +
// 			       trak.duration.getSeconds());
	    
	    if (trak.buildSyncTable()) {
		keyFrameTrack = i;
	    }
	    // System.out.println("$$$$ Call buildSamplePerChunkTable for track id " + trak.id);
	    trak.buildSamplePerChunkTable();
	    // Table is built for VIDEO and hint tracks but not
	    // for audio tracks.
	    if ( !trak.trackType.equals(AUDIO) ) {
		trak.buildSampleOffsetTable();

//  		System.out.println("Creating buildSampleOffsetTable for track " +
// 				   trak.trackType + " : " +
// 				   trak.sampleOffsetTable);

		trak.buildStartTimeAndDurationTable();

		float frameRate = (float) (trak.numberOfSamples /
		                     trak.duration.getSeconds());
		//$$$$$		((Video) trak.media).frameRate = frameRate;
		trak.media.frameRate = frameRate;

	    }
	    // NOTE: The next method should be called after buildSampleOffsetTable()
	    trak.buildCumulativeSamplePerChunkTable();
	    trak.media.createFormat();
	    // System.out.println("track " + (i+1) + " info: ");
	    // System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
	    // System.out.println(trak);
	    // System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
	}
    
public javax.media.TimesetPosition(javax.media.Time where, int rounding)


	double time = where.getSeconds();
	if (time < 0)
	    time = 0;
// 	if ( (keyFrameTrack != -1) && (tracks[keyFrameTrack].isEnabled()) ) {

	int keyT;

	if ( (((keyT = keyFrameTrack) != -1) && (tracks[keyFrameTrack].isEnabled())) ||
	     (((keyT = hintAudioTrackNum) != -1) && (tracks[hintAudioTrackNum].isEnabled())) ) {

			       
	    TrakList trakInfo = trakList[keyT];
	    int index = trakInfo.time2Index(time);

	    if (index < 0) {
		((MediaTrack)tracks[keyT]).setSampleIndex(trakInfo.numberOfSamples + 1); // past eom
	    } else {
		int syncIndex;
		
		if (keyT == keyFrameTrack) {
		    if (index >= trakInfo.syncSampleMapping.length) {
			index = trakInfo.syncSampleMapping.length - 1;
		    }
		    if (trakInfo.syncSampleMapping != null) {
			syncIndex = trakInfo.syncSampleMapping[index];
			double newtime = trakInfo.index2TimeAndDuration(syncIndex).startTime;
			time = newtime;
		    } else {
			// Note: you won't come here because syncSampleMapping wont
			// be null in this case.
			syncIndex = index;
		    }
		} else { // hint audio track
		    syncIndex = index;
		    double newtime = trakInfo.index2TimeAndDuration(syncIndex).startTime;
		    time = newtime;
		}
		((MediaTrack)tracks[keyT]).setSampleIndex(syncIndex);
	    }
	}

	for (int i = 0; i < numSupportedTracks; i++) {
	    if (i == keyT)
		continue;


	    if (!tracks[i].isEnabled())
		continue;

	    // TODO: See if you can just call a setPosition or
	    // setIndex method for each media type, instead of
	    // using if statement
	    TrakList trakInfo = trakList[i];
	    // Note that the time here may not be the same as the
	    // the "Time where" parameter passed into this method.
	    // The time may be changed if it doesn't map to a keyFrame
	    // in the Video track.
	    int index = trakInfo.time2Index(time);

// 	    if ( trakInfo.trackType.equals(VIDEO) || 
// 		 trakInfo.trackType.equals(HINT) ) {

	    if ( trakInfo.trackType.equals(VIDEO) || 
		 ( trakInfo.trackType.equals(HINT) &&
		   (tracks[i] instanceof HintVideoTrack)) ) {

		if (index < 0) {
		    ((MediaTrack)tracks[i]).setSampleIndex(trakInfo.numberOfSamples + 1); // past eom
		} else {
		    int syncIndex;
		    if (trakInfo.syncSampleMapping != null) {
			syncIndex = trakInfo.syncSampleMapping[index];
		    } else
			syncIndex = index;
		    ((MediaTrack)tracks[i]).setSampleIndex(syncIndex);
		}
	    } else { // TODO: if you have other track types, then check for AUDIO here

		if (index < 0) {
		    ((MediaTrack)tracks[i]).setChunkNumber(trakInfo.numberOfChunks + 1); // past eom
		} else {
		    int sampleOffsetInChunk;
		    
		    ((MediaTrack)tracks[i]).setSampleIndex(index);
		    // $$$$$ IMPORTANT TODO: fix this as the index2Chunk method
		    // takes index starting from 1, not 0
		    int chunkNumber = trakInfo.index2Chunk(index);
		    
		    if (chunkNumber != 0) {
			if ( trakInfo.constantSamplesPerChunk == -1) {
			    // Note samplesPerChunk array contains cumulative
			    // samples per chunk
			    sampleOffsetInChunk = index -
				trakInfo.samplesPerChunk[chunkNumber-1];
			} else {
			    // TODO: need to test this case
			    sampleOffsetInChunk = index -
				chunkNumber *
				trakInfo.constantSamplesPerChunk;
			}
		    } else {
			sampleOffsetInChunk = index;
		    }
		    ((AudioTrack)tracks[i]).setChunkNumberAndSampleOffset(chunkNumber,
									  sampleOffsetInChunk);
		}
	    }
	}
	if (cacheStream != null) {
	    synchronized(this) {
		cacheStream.abortRead();
	    }
	}
	synchronized(mediaTime) {
	    mediaTime.set(time);
	}
	return mediaTime;
    
public voidsetSource(javax.media.protocol.DataSource source)


	super.setSource(source);
	stream = (PullSourceStream) streams[0];
	seekableStream = (Seekable) streams[0];
    
private voidskipAtom(java.lang.String atom, int size)

	if (debug2)
	    System.out.println("skip unsupported atom " + atom);
	skip(stream, size);
    
protected booleansupports(javax.media.protocol.SourceStream[] s)
Quicktime format requires that the stream be seekable and random accessible.

                     
        
	return seekable;