/* * $ProjectName$ * $ProjectRevision$ * ----------------------------------------------------------- * $Id: CachedUrlStream.java,v 1.1 2003/04/10 19:48:22 jarnbjo Exp $ * ----------------------------------------------------------- * * $Author: jarnbjo $ * * Description: * * Copyright 2002-2003 Tor-Einar Jarnbjo * ----------------------------------------------------------- * * Change History * ----------------------------------------------------------- * $Log: CachedUrlStream.java,v $ * Revision 1.1 2003/04/10 19:48:22 jarnbjo * no message * * */ package sound.jarnbjo.ogg; import java.io.*; import java.net.*; import java.util.*; /** * Implementation of the <code>PhysicalOggStream</code> interface for reading * and caching an Ogg stream from a URL. This class reads the data as fast as * possible from the URL, caches it locally either in memory or on disk, and * supports seeking within the available data. */ public class CachedUrlStream implements PhysicalOggStream { private boolean closed=false; private URLConnection source; private InputStream sourceStream; private Object drainLock=new Object(); private RandomAccessFile drain; private byte[] memoryCache; private ArrayList pageOffsets=new ArrayList(); private ArrayList pageLengths=new ArrayList(); private long numberOfSamples=-1; private long cacheLength; private HashMap logicalStreams=new HashMap(); private LoaderThread loaderThread; /** * Creates an instance of this class, using a memory cache. */ public CachedUrlStream(URL source) throws OggFormatException, IOException { this(source, null); } /** * Creates an instance of this class, using the specified file as cache. The * file is not automatically deleted when this class is disposed. */ public CachedUrlStream(URL source, RandomAccessFile drain) throws OggFormatException, IOException { this.source=source.openConnection(); if(drain==null) { int contentLength=this.source.getContentLength(); if(contentLength==-1) { throw new IOException("The URLConncetion's content length must be set when operating with a in-memory cache."); } memoryCache=new byte[contentLength]; } this.drain=drain; this.sourceStream=this.source.getInputStream(); loaderThread=new LoaderThread(sourceStream, drain, memoryCache); new Thread(loaderThread).start(); while(!loaderThread.isBosDone() || pageOffsets.size()<20) { //System.out.print("pageOffsets.size(): "+pageOffsets.size()+"\r"); try { Thread.sleep(200); } catch (InterruptedException ex) { } } //System.out.println(); //System.out.println("caching "+pageOffsets.size()+"/20 pages\r"); } public Collection getLogicalStreams() { return logicalStreams.values(); } public boolean isOpen() { return !closed; } public void close() throws IOException { closed=true; sourceStream.close(); } public long getCacheLength() { return cacheLength; } /* private OggPage getNextPage() throws EndOfOggStreamException, IOException, OggFormatException { return getNextPage(false); } private OggPage getNextPage(boolean skipData) throws EndOfOggStreamException, IOException, OggFormatException { return OggPage.create(sourceStream, skipData); } */ public OggPage getOggPage(int index) throws IOException { synchronized(drainLock) { Long offset=(Long)pageOffsets.get(index); Long length=(Long)pageLengths.get(index); if(offset!=null) { if(drain!=null) { drain.seek(offset.longValue()); return OggPage.create(drain); } else { byte[] tmpArray=new byte[length.intValue()]; System.arraycopy(memoryCache, offset.intValue(), tmpArray, 0, length.intValue()); return OggPage.create(tmpArray); } } else { return null; } } } private LogicalOggStream getLogicalStream(int serialNumber) { return (LogicalOggStream)logicalStreams.get(new Integer(serialNumber)); } public void setTime(long granulePosition) throws IOException { for(Iterator iter=logicalStreams.values().iterator(); iter.hasNext(); ) { LogicalOggStream los=(LogicalOggStream)iter.next(); los.setTime(granulePosition); } } public class LoaderThread implements Runnable { private InputStream source; private RandomAccessFile drain; private byte[] memoryCache; private boolean bosDone=false; private int pageNumber; public LoaderThread(InputStream source, RandomAccessFile drain, byte[] memoryCache) { this.source=source; this.drain=drain; this.memoryCache=memoryCache; } public void run() { try { boolean eos=false; byte[] buffer=new byte[8192]; while(!eos) { OggPage op=OggPage.create(source); synchronized (drainLock) { int listSize=pageOffsets.size(); long pos= listSize>0? ((Long)pageOffsets.get(listSize-1)).longValue()+ ((Long)pageLengths.get(listSize-1)).longValue(): 0; byte[] arr1=op.getHeader(); byte[] arr2=op.getSegmentTable(); byte[] arr3=op.getData(); if(drain!=null) { drain.seek(pos); drain.write(arr1); drain.write(arr2); drain.write(arr3); } else { System.arraycopy(arr1, 0, memoryCache, (int)pos, arr1.length); System.arraycopy(arr2, 0, memoryCache, (int)pos+arr1.length, arr2.length); System.arraycopy(arr3, 0, memoryCache, (int)pos+arr1.length+arr2.length, arr3.length); } pageOffsets.add(new Long(pos)); pageLengths.add(new Long(arr1.length+arr2.length+arr3.length)); } if(!op.isBos()) { bosDone=true; //System.out.println("bosDone=true;"); } if(op.isEos()) { eos=true; } LogicalOggStreamImpl los=(LogicalOggStreamImpl)getLogicalStream(op.getStreamSerialNumber()); if(los==null) { los=new LogicalOggStreamImpl(CachedUrlStream.this, op.getStreamSerialNumber()); logicalStreams.put(new Integer(op.getStreamSerialNumber()), los); los.checkFormat(op); } los.addPageNumberMapping(pageNumber); los.addGranulePosition(op.getAbsoluteGranulePosition()); pageNumber++; cacheLength=op.getAbsoluteGranulePosition(); //System.out.println("read page: "+pageNumber); } } catch(EndOfOggStreamException e) { // ok } catch(IOException e) { e.printStackTrace(); } } public boolean isBosDone() { return bosDone; } } public boolean isSeekable() { return true; } }