Package net.sf.fmj.media

Source Code of net.sf.fmj.media.CloneablePushDataSource$ClonedDataSource$ClonedPushSourceStream

package net.sf.fmj.media;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.Buffer;
import javax.media.Time;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushDataSource;
import javax.media.protocol.PushSourceStream;
import javax.media.protocol.SourceCloneable;
import javax.media.protocol.SourceTransferHandler;

import net.sf.fmj.utility.LoggerSingleton;

import com.lti.utils.synchronization.ProducerConsumerQueue;

/**
* Cloneable {@link PushDataSource}.
* @author Ken Larson
*
*/
public class CloneablePushDataSource extends PushDataSource implements SourceCloneable
{
  // TODO: we should be more efficient in our use of buffers.  We currently clone each buffer
  // once for each clone.  Perhaps the buffers could be simply stored in a multi-consumer queue?
  // one of the problems with this is that I don't believe a Buffer is considered to be
  // immutable by the filter graph process.  This is because a codec can choose to consume
  // only part of the input, and then update the offset/length to reflect what it has consumed.
  // also data swapping between buffers can occur.
  private static final Logger logger = LoggerSingleton.logger;
 
  private final PushDataSource source;
  private PushSourceStream[] streams;
  private final ClonedDataSource firstClonedDataSource;

  public CloneablePushDataSource(PushDataSource source)
  {
    super();
    this.source = source;
    firstClonedDataSource = (ClonedDataSource) createClone();
  }

  @Override
  public PushSourceStream[] getStreams()
  {
    return firstClonedDataSource.getStreams();
  }


  @Override
  public void connect() throws IOException
  {
    firstClonedDataSource.connect();
  }

  @Override
  public void disconnect()
  {
    firstClonedDataSource.disconnect();
  }

  @Override
  public String getContentType()
  {
    return firstClonedDataSource.getContentType();
  }

  @Override
  public Object getControl(String controlType)
  {
    return firstClonedDataSource.getControl(controlType);
  }

  @Override
  public Object[] getControls()
  {
    return firstClonedDataSource.getControls();
  }

  @Override
  public Time getDuration()
  {
    return firstClonedDataSource.getDuration();
  }

  @Override
  public void start() throws IOException
  {
    firstClonedDataSource.start();
  }

  @Override
  public void stop() throws IOException
  {
    firstClonedDataSource.stop();
  }
 
  private List<ClonedDataSource> clones = new ArrayList<ClonedDataSource>();
  public synchronized DataSource createClone()
  {
    final ClonedDataSource result = new ClonedDataSource();
    clones.add(result);
    return result;
  }
 
  private boolean sourceConnected = false;
  private boolean sourceStarted = false;
 
  private static final int READ_BUFFER_SIZE = 2000; // TODO: how to determine? // TODO: take into account minimu transfer size?

  private class MySourceTransferHandler implements SourceTransferHandler
  {
   
    private final int streamIndex;
   
    public MySourceTransferHandler(int streamIndex)
    {
      super();
      this.streamIndex = streamIndex;
    }

    public void transferData(PushSourceStream stream)
    {
      final Buffer originalBuffer = new Buffer()// TODO: find a way to reuse buffers/avoid allocating new memory each time
     
      try
      {
        final byte[] originalBufferData = new byte[READ_BUFFER_SIZE];
        originalBuffer.setData(originalBufferData);
        int numRead = stream.read(originalBufferData, 0, originalBufferData.length);
        if (numRead < 0)
          originalBuffer.setEOM(true);
        else
          originalBuffer.setLength(numRead);       
      } catch (IOException e)
      {
        originalBuffer.setEOM(true)// TODO
        originalBuffer.setDiscard(true)// TODO
        originalBuffer.setLength(0);
        if (!(e instanceof InterruptedIOException))  // logging interruptions is noisy.
          logger.log(Level.WARNING, "" + e, e);
      }
     
      final List<ClonedDataSource.ClonedPushSourceStream> clonedStreams = new ArrayList<ClonedDataSource.ClonedPushSourceStream>();
      synchronized (CloneablePushDataSource.this)
      {
        for (ClonedDataSource clone : clones)
        {
          final ClonedDataSource.ClonedPushSourceStream clonedStream = (ClonedDataSource.ClonedPushSourceStream) clone.getStreams()[streamIndex];
          clonedStreams.add(clonedStream);
         
        }
      }
     
      // TODO: additional synchronization?
      try
      {
        // put a clone of the buffer in each stream's buffer queue
        for (ClonedDataSource.ClonedPushSourceStream clonedStream : clonedStreams)
        {
          clonedStream.getBufferQueue().put(originalBuffer.clone());
         
        }
       
        // notify their transfer handlers asynchronously:
        for (ClonedDataSource.ClonedPushSourceStream clonedStream : clonedStreams)
        {
          clonedStream.notifyTransferHandlerAsync();
         
        }
       
       
      }
      catch (InterruptedException e)
      {
        logger.log(Level.WARNING, "" + e, e);
        return;
      }
     
     
    }
  }
 
  private class ClonedDataSource extends PushDataSource
  {

    private ClonedPushSourceStream[] clonedStreams;
    private boolean cloneConnected;
    private boolean cloneStarted;
   
   
    @Override
    public PushSourceStream[] getStreams()
    {
      synchronized (CloneablePushDataSource.this)
      {
        if (clonedStreams == null)
        { 
          clonedStreams = new ClonedPushSourceStream[streams.length];
          for (int i = 0; i < streams.length; ++i)
          {
            clonedStreams[i] = new ClonedPushSourceStream(streams[i]);
          }
        }
        return clonedStreams;
      }
    }

    @Override
    public void connect() throws IOException
    {
     
      synchronized (CloneablePushDataSource.this)
      {
        if (cloneConnected)
          return;
       
        if (!sourceConnected)
        {  source.connect();
          sourceConnected = true;
        }
       
        cloneConnected = true;
      }
    }

    @Override
    public void disconnect()
    {
      boolean disposeAllClones = false;
     
      synchronized (CloneablePushDataSource.this)
      {
        if (!cloneConnected)
          return;
        cloneConnected = false;
       
        if (sourceConnected// should always be true if the clone was connected...
        {
          // stop underlying source if needed:
          for (ClonedDataSource clone : clones)
          {
            if (clone.cloneConnected)
              return;    // at least one started, don't close underlying source
          }
         
          source.disconnect();
         
          for (ClonedDataSource clone : clones)
            clone.disposeAsync();
         
          sourceConnected = false;
        }
       
      }
     
      if (disposeAllClones)
      {
      }
    }

    @Override
    public String getContentType()
    {
      synchronized (CloneablePushDataSource.this)
      {  return source.getContentType();
      }
    }

    @Override
    public Object getControl(String controlType)
    {
      synchronized (CloneablePushDataSource.this)
      {  return source.getControl(controlType);
      }
    }

    @Override
    public Object[] getControls()
    {
      synchronized (CloneablePushDataSource.this)
      {  return source.getControls();
      }
    }

    @Override
    public Time getDuration()
    {
      synchronized (CloneablePushDataSource.this)
      {  return source.getDuration();
      }
    }

    @Override
    public void start() throws IOException
    {
      // TODO: only start this data source?
      synchronized (CloneablePushDataSource.this)
      {
        if (cloneStarted)
          return;
        if (!sourceStarted)
        { 
          streams = source.getStreams();
          for (int i = 0; i < streams.length; ++i)
          {
            streams[i].setTransferHandler(new MySourceTransferHandler(i));
          }
          source.start();
       
          sourceStarted = true;
        }
       
        cloneStarted = true;
      }
    }

    @Override
    public void stop() throws IOException
    {
      synchronized (CloneablePushDataSource.this)
      {
        if (!cloneStarted)
          return;
        cloneStarted = false;
       
        if (sourceStarted) // should always be true if the clone was started
        {
          // stop underlying source if needed:
          for (ClonedDataSource clone : clones)
          {
            if (clone.cloneStarted)
              return;    // at least one started, don't close underlying source
          }
         
          source.stop();
         
          sourceStarted = false;
        }
       
      }
     
    }
   
    public void disposeAsync()
    {
      for (ClonedPushSourceStream clonedStream : clonedStreams)
      {  clonedStream.disposeAsync();
      }
    }
   
    class ClonedPushSourceStream implements PushSourceStream
    {
      private final PushSourceStream stream;
      private final ProducerConsumerQueue bufferQueue = new ProducerConsumerQueue();    // TODO: limit size?
      private final BufferQueueInputStream bufferQueueInputStream = new BufferQueueInputStream(bufferQueue);
     
      private boolean eos = false;
     
      public ClonedPushSourceStream(PushSourceStream stream)
      {
        super();
        this.stream = stream;
      }
     
      ProducerConsumerQueue getBufferQueue()
      {  return bufferQueue;
      }

      public boolean endOfStream()
      {
        return eos;
      }

      public ContentDescriptor getContentDescriptor()
      {
        synchronized (CloneablePushDataSource.this)
        {  return stream.getContentDescriptor();
        }
      }

      public long getContentLength()
      {
        synchronized (CloneablePushDataSource.this)
        {  return stream.getContentLength();
        }
      }

      public int getMinimumTransferSize()
      {
        synchronized (CloneablePushDataSource.this)
        {  return stream.getMinimumTransferSize();
        }
      }

      public Object getControl(String controlType)
      {
        synchronized (CloneablePushDataSource.this)
        {  return stream.getControl(controlType);
        }
      }

      public Object[] getControls()
      {
        synchronized (CloneablePushDataSource.this)
        {  return stream.getControls();
        }
      }

      public int read(byte[] buffer, int offset, int length) throws IOException
      {
        if (bufferQueue.isEmpty())
          return 0;
        return bufferQueueInputStream.read(buffer, offset, length);
      }
     
      private final AsyncSourceTransferHandlerNotifier asyncSourceTransferHandlerNotifier = new AsyncSourceTransferHandlerNotifier(this);

      public void setTransferHandler(SourceTransferHandler transferHandler)
      {
        asyncSourceTransferHandlerNotifier.setTransferHandler(transferHandler);
      }

      public void notifyTransferHandlerAsync() throws InterruptedException
      {
        // TODO: looser synchronization?
        synchronized (CloneablePushDataSource.this)
        {
          if (!cloneStarted)
            return;
          asyncSourceTransferHandlerNotifier.notifyTransferHandlerAsync();
        }
      }

      public void dispose()
      {
        asyncSourceTransferHandlerNotifier.dispose();
      }
     
      public void disposeAsync()
      {
        asyncSourceTransferHandlerNotifier.disposeAsync();
      }

   
     
    }
   
  }
 

 
}
TOP

Related Classes of net.sf.fmj.media.CloneablePushDataSource$ClonedDataSource$ClonedPushSourceStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.