/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.media.jfxmediaimpl; import java.io.IOException; import java.io.EOFException; import java.nio.ByteBuffer; import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.lang.ref.WeakReference; import java.util.ListIterator; import com.sun.media.jfxmedia.locator.Locator; import com.sun.media.jfxmedia.locator.ConnectionHolder; import com.sun.media.jfxmedia.events.MetadataListener; import java.nio.charset.Charset; import java.util.Collections; public abstract class MetadataParserImpl extends Thread implements com.sun.media.jfxmedia.MetadataParser { private String[] FLV_VIDEO_CODEC_NAME = { "Unsupported", "JPEG Video (Unsupported)", "Sorenson H.263 Video", "Flash Screen Video", "On2 VP6 Video", "On2 VP6-Alpha Video", "Unsupported", "H.264 Video", "Unsupported", "Unsupported", "Unsupported", "Unsupported", "Unsupported", "Unsupported", "Unsupported", "Unsupported" }; private final List> listeners = new ArrayList>(); private Map metadata = new HashMap(); private Locator locator = null; private ConnectionHolder connectionHolder = null; private ByteBuffer buffer = null; private Map rawMetaMap = null; protected ByteBuffer rawMetaBlob = null; private boolean parsingRawMetadata = false; private int length = 0; private int index = 0; private int streamPosition = 0; public MetadataParserImpl(Locator locator) { this.locator = locator; } public void addListener(MetadataListener listener) { synchronized (listeners) { if (listener != null) { listeners.add(new WeakReference(listener)); } } } public void removeListener(MetadataListener listener) { synchronized (listeners) { if (listener != null) { for (ListIterator> it = listeners.listIterator(); it.hasNext();) { MetadataListener l = it.next().get(); if (l == null || l == listener) { it.remove(); } } } } } public void startParser() throws IOException { start(); } public void stopParser() { if (connectionHolder != null) { connectionHolder.closeConnection(); } } @Override public void run() { try { connectionHolder = locator.createConnectionHolder(); parse(); } catch (IOException e) { } } abstract protected void parse(); protected void addMetadataItem(String tag, Object value) { metadata.put(tag, value); } protected void done() { synchronized (listeners) { if (!metadata.isEmpty()) { for (ListIterator> it = listeners.listIterator(); it.hasNext();) { MetadataListener l = it.next().get(); if (l != null) { l.onMetadata(metadata); } else { it.remove(); } } } } } protected int getStreamPosition() { if (parsingRawMetadata) { return rawMetaBlob.position(); } return streamPosition; } protected void startRawMetadata(int sizeHint) { rawMetaBlob = ByteBuffer.allocate(sizeHint); } private void adjustRawMetadataSize(int addSize) { // resize if necessary (expensive!) if (rawMetaBlob.remaining() < addSize) { int pos = rawMetaBlob.position(); int newSize = pos + addSize; ByteBuffer newBuffer = ByteBuffer.allocate(newSize); rawMetaBlob.position(0); newBuffer.put(rawMetaBlob.array(), 0, pos); rawMetaBlob = newBuffer; } } /* Read from the source stream directly into the raw metadata blob */ protected void readRawMetadata(int size) throws IOException { byte[] data = getBytes(size); adjustRawMetadataSize(size); if (null != data) { rawMetaBlob.put(data); } } /* Use this to put data that's already been read back into the metadata blob */ protected void stuffRawMetadata(byte[] data, int offset, int size) { if (null != rawMetaBlob) { adjustRawMetadataSize(size); rawMetaBlob.put(data, offset, size); } } protected void disposeRawMetadata() { parsingRawMetadata = false; rawMetaBlob = null; } // Switch from reading from the soure to using the raw metadata blob protected void setParseRawMetadata(boolean state) { if (null == rawMetaBlob) { parsingRawMetadata = false; return; } if (state) { rawMetaBlob.position(0); } parsingRawMetadata = state; } // Add the current raw metadata blob to the metadata map protected void addRawMetadata(String type) { if (null == rawMetaBlob) { return; } if (null == rawMetaMap) { rawMetaMap = new HashMap(); // make sure the map we add to the metadata is read-only metadata.put(RAW_METADATA_TAG_NAME, Collections.unmodifiableMap(rawMetaMap)); } rawMetaMap.put(type, rawMetaBlob.asReadOnlyBuffer()); } protected void skipBytes(int num) throws IOException, EOFException { if (parsingRawMetadata) { rawMetaBlob.position(rawMetaBlob.position()+num); return; } for (int i = 0; i < num; i++) { getNextByte(); } } protected byte getNextByte() throws IOException, EOFException { if (parsingRawMetadata) { // read from the raw metadata blob, not from the stream return rawMetaBlob.get(); } if (buffer == null) { buffer = connectionHolder.getBuffer(); length = connectionHolder.readNextBlock(); } if (index >= length) { length = connectionHolder.readNextBlock(); if (length < 1) { throw new EOFException(); } index = 0; } byte b = buffer.get(index); index++; streamPosition++; return b; } protected byte[] getBytes(int size) throws IOException, EOFException { byte[] bytes = new byte[size]; if (parsingRawMetadata) { rawMetaBlob.get(bytes); return bytes; } for (int i = 0; i < size; i++) { bytes[i] = getNextByte(); } return bytes; } protected long getLong() throws IOException, EOFException { if (parsingRawMetadata) { return rawMetaBlob.getLong(); } long value = 0; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); return value; } protected int getInteger() throws IOException, EOFException { if (parsingRawMetadata) { return rawMetaBlob.getInt(); } int value = 0; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); value = value << 8; value |= (getNextByte() & 0xFF); return value; } protected short getShort() throws IOException, EOFException { if (parsingRawMetadata) { return rawMetaBlob.getShort(); } short value = 0; value |= (getNextByte() & 0xFF); value = (short) (value << 8); value |= (getNextByte() & 0xFF); return value; } protected double getDouble() throws IOException, EOFException { if (parsingRawMetadata) { return rawMetaBlob.getDouble(); } long bits = getLong(); return Double.longBitsToDouble(bits); } protected String getString(int length, Charset charset) throws IOException, EOFException { byte[] bytes = getBytes(length); return new String(bytes, 0, length, charset); } protected int getU24() throws IOException, EOFException { int value = 0; value |= (getNextByte() & 0xFF); value = (int) (value << 8); value |= (getNextByte() & 0xFF); value = (int) (value << 8); value |= (getNextByte() & 0xFF); return value; } // XXX Change hard-coded strings to constants defined in MetadataParser. protected Object convertValue(String tag, Object value) { if (tag.equals("duration") && value instanceof Double) { Double v = ((Double) value * 1000); return v.longValue(); } else if (tag.equals("duration") && value instanceof String) { String v = (String) value; return Long.valueOf(v.trim()); } else if (tag.equals("width") || tag.equals("height")) { Double v = (Double) value; return v.intValue(); } else if (tag.equals("framerate")) { return value; } else if (tag.equals("videocodecid")) { // XXX: This conversion to String should be in the FLV parser itself. int codecid = ((Double) value).intValue(); if (codecid < FLV_VIDEO_CODEC_NAME.length) { return FLV_VIDEO_CODEC_NAME[codecid]; } else { return null; } } else if (tag.equals("audiocodecid")) { // XXX hard-coded return "MPEG 1 Audio"; } else if (tag.equals("creationdate")) { return ((String) value).trim(); } else if (tag.equals("track number") || tag.equals("disc number")) { String[] v = ((String) value).split("/"); if (v.length == 2) { return Integer.valueOf(v[0].trim()); } } else if (tag.equals("track count") || tag.equals("disc count")) { String[] tc = ((String) value).split("/"); if (tc.length == 2) { return Integer.valueOf(tc[1].trim()); } } else if (tag.equals("album")) { return value; } else if (tag.equals("artist")) { return value; } else if (tag.equals("genre")) { return value; } else if (tag.equals("title")) { return value; } else if (tag.equals("album artist")) { return value; } else if (tag.equals("comment")) { return value; } else if (tag.equals("composer")) { return value; } else if (tag.equals("year")) { String v = (String) value; return Integer.valueOf(v.trim()); } return null; } }