public VideoTrack(PullSourceStream stream) throws ResourceUnavailableException {
      super();

      this.stream = stream;
      // set format

      // read first frame to determine format
      final Buffer buffer = new Buffer();
      readFrame(buffer);
      if (buffer.isDiscard() || buffer.isEOM())
        throw new ResourceUnavailableException("Unable to read first frame");
      // TODO: catch runtime exception too?

      // parse jpeg
      final java.awt.Image image;
      try {
        image =
            ImageIO.read(
                new ByteArrayInputStream(
                    (byte[]) buffer.getData(), buffer.getOffset(), buffer.getLength()));
      } catch (IOException e) {
        logger.log(Level.WARNING, "" + e, e);
        throw new ResourceUnavailableException("Error reading image: " + e);
      }

      if (image == null) {
        logger.log(Level.WARNING, "Failed to read image (ImageIO.read returned null).");
        throw new ResourceUnavailableException();
      }

      if (frameContentType.equals("image/jpeg"))
        format =
            new JPEGFormat(
                new Dimension(image.getWidth(null), image.getHeight(null)),
                Format.NOT_SPECIFIED,
                Format.byteArray,
                -1.f,
                Format.NOT_SPECIFIED,
                Format.NOT_SPECIFIED);
      else if (frameContentType.equals("image/gif"))
        format =
            new GIFFormat(
                new Dimension(image.getWidth(null), image.getHeight(null)),
                Format.NOT_SPECIFIED,
                Format.byteArray,
                -1.f);
      else if (frameContentType.equals("image/png"))
        format =
            new PNGFormat(
                new Dimension(image.getWidth(null), image.getHeight(null)),
                Format.NOT_SPECIFIED,
                Format.byteArray,
                -1.f);
      else
        throw new ResourceUnavailableException(
            "Unsupported frame content type: " + frameContentType);
      // TODO: this discards first image. save and return first time
      // readFrame is called.

    }
  // @Override
  @Override
  public int read() throws IOException {
    // TODO: how do we detect IOException?
    fillBuffer();
    if (buffer.getLength() == 0 && buffer.isEOM()) // TODO: will always be
      // EOM if length is 0
      return -1;
    final byte[] data = (byte[]) buffer.getData();
    final int result = data[buffer.getOffset()] & 0xff;
    buffer.setOffset(buffer.getOffset() + 1);
    buffer.setLength(buffer.getLength() - 1);

    return result;
  }
  // @Override
  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    // TODO: how do we detect IOException?
    fillBuffer();
    if (buffer.getLength() == 0 && buffer.isEOM()) // TODO: will always be
      // EOM if length is 0
      return -1;
    final byte[] data = (byte[]) buffer.getData();

    int lengthToCopy = buffer.getLength() < len ? buffer.getLength() : len;
    System.arraycopy(data, buffer.getOffset(), b, off, lengthToCopy);
    buffer.setOffset(buffer.getOffset() + lengthToCopy);
    buffer.setLength(buffer.getLength() - lengthToCopy);

    return lengthToCopy;
  }
  private void fillBuffer() {
    if (buffer == null) {
      buffer = new Buffer();
      buffer.setFormat(track.getFormat());
    }

    do {
      if (buffer.isEOM()) return;

      if (buffer.getLength() > 0) return; // still have data in buffer
      // TODO: any fields to set?

      track.readFrame(buffer);
      logger.fine("Read buffer from track: " + buffer.getLength());

    } while (buffer.isDiscard());
  }
    @Override
    public void read(Buffer buffer) throws IOException {
      pbs.read(buffer);

      // Remap the time stamps so it won't wrap around
      // while changing to a new file.
      if (buffer.getTimeStamp() != Buffer.TIME_UNKNOWN) {
        long diff = buffer.getTimeStamp() - lastTS;
        lastTS = buffer.getTimeStamp();
        if (diff > 0) timeStamp += diff;
        buffer.setTimeStamp(timeStamp);
      }

      // If this track is to be used as the master time base,
      // we'll need to compute the master time based on this track.
      if (useAsMaster) {
        if (buffer.getFormat() instanceof AudioFormat) {
          AudioFormat af = (AudioFormat) buffer.getFormat();
          masterAudioLen += buffer.getLength();
          long t = af.computeDuration(masterAudioLen);
          if (t > 0) {
            masterTime = t;
          } else {
            masterTime = buffer.getTimeStamp();
          }
        } else {
          masterTime = buffer.getTimeStamp();
        }
      }

      if (buffer.isEOM()) {
        tInfo.done = true;
        if (!ds.handleEOM(tInfo)) {
          // This is not the last processor to be done.
          // We'll need to un-set the EOM flag.
          buffer.setEOM(false);
          buffer.setDiscard(true);
        }
      }
    }
  /** Write the buffer to the SourceDataLine. */
  public int process(Buffer buffer) {

    // if we need to convert the format, do so using the codec.
    if (codec != null) {
      final int codecResult = codec.process(buffer, codecBuffer);
      if (codecResult == BUFFER_PROCESSED_FAILED) return BUFFER_PROCESSED_FAILED;
      if (codecResult == OUTPUT_BUFFER_NOT_FILLED) return BUFFER_PROCESSED_OK;
      buffer = codecBuffer;
    }

    int length = buffer.getLength();
    int offset = buffer.getOffset();

    final Format format = buffer.getFormat();

    final Class type = format.getDataType();
    if (type != Format.byteArray) {
      return BUFFER_PROCESSED_FAILED;
    }

    final byte[] data = (byte[]) buffer.getData();

    final boolean bufferNotConsumed;
    final int newBufferLength; // only applicable if bufferNotConsumed
    final int newBufferOffset; // only applicable if bufferNotConsumed

    if (NON_BLOCKING) {
      // TODO: handle sourceLine.available().  This code currently causes choppy audio.

      if (length > sourceLine.available()) {
        // we should only write sourceLine.available() bytes, then return INPUT_BUFFER_NOT_CONSUMED.
        length = sourceLine.available(); // don't try to write more than available
        bufferNotConsumed = true;
        newBufferLength = buffer.getLength() - length;
        newBufferOffset = buffer.getOffset() + length;

      } else {
        bufferNotConsumed = false;
        newBufferLength = length;
        newBufferOffset = offset;
      }
    } else {
      bufferNotConsumed = false;
      newBufferLength = 0;
      newBufferOffset = 0;
    }

    if (length == 0) {
      logger.finer("Buffer has zero length, flags = " + buffer.getFlags());
    }

    // make sure all the bytes are written.
    while (length > 0) {

      // logger.fine("Available: " + sourceLine.available());
      // logger.fine("length: " + length);
      // logger.fine("sourceLine.getBufferSize(): " + sourceLine.getBufferSize());

      final int n =
          sourceLine.write(
              data, offset, length); // TODO: this can block for a very long time if it doesn't
      if (n >= length) break;
      else if (n == 0) {
        // TODO: we could choose to handle a write failure this way,
        // assuming that it is considered legal to call stop while process is being called.
        // however, that seems like a bad idea in general.
        //				if (!sourceLine.isRunning())
        //				{
        //					buffer.setLength(offset);
        //					buffer.setOffset(length);
        //					return INPUT_BUFFER_NOT_CONSUMED;	// our write was interrupted.
        //				}

        logger.warning(
            "sourceLine.write returned 0, offset="
                + offset
                + "; length="
                + length
                + "; available="
                + sourceLine.available()
                + "; frame size in bytes"
                + sourceLine.getFormat().getFrameSize()
                + "; sourceLine.isActive() = "
                + sourceLine.isActive()
                + "; "
                + sourceLine.isOpen()
                + "; sourceLine.isRunning()="
                + sourceLine.isRunning());
        return BUFFER_PROCESSED_FAILED; // sourceLine.write docs indicate that this will only happen
                                        // if there is an error.

      } else {
        offset += n;
        length -= n;
      }
    }

    if (bufferNotConsumed) {
      // return INPUT_BUFFER_NOT_CONSUMED if not all bytes were written

      buffer.setLength(newBufferLength);
      buffer.setOffset(newBufferOffset);
      return INPUT_BUFFER_NOT_CONSUMED;
    }

    if (buffer.isEOM()) {
      // TODO: the proper way to do this is to implement Drainable, and let the processor call our
      // drain method.
      sourceLine
          .drain(); // we need to ensure that the media finishes playing, otherwise the EOM event
                    // will
      // be posted before the media finishes playing.
    }

    return BUFFER_PROCESSED_OK;
  }