/*
* $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 de.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;
}
}