/**
* Copyright 2014 Comcast Cable Communications Management, LLC
*
* This file is part of CATS.
*
* CATS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CATS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CATS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.comcast.cats.video.service;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import javax.imageio.ImageReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.stream.ImageInputStream;
import java.util.Collection;
import javax.imageio.ImageIO;
import org.apache.log4j.Logger;
import java.io.DataInputStream;
/**
* This class provides low level control and connectivity to the video device.
*
* @author jtyrre001
*/
public class AxisServerConnection
{
/**
* url of axis video device.
*/
private URL url;
/**
* Data input stream.
*/
private DataInputStream dis;
/**
* Buffered image captured from video device.
*/
private BufferedImage image;
/**
* HTTP connection handle.
*/
private HttpURLConnection huc;
/**
* Image reader handle.
*/
private ImageReader imageDecoder;
/**
* Image input stream handle.
*/
private ImageInputStream iis;
/**
* video device connectivity flag.
*/
private boolean connected;
AxisVideoSize DEFAULT_VIDEO_SIZE = AxisVideoSize.AXIS_4CIF;
private static final Logger logger = Logger.getLogger( AxisServerConnection.class );
private String connectedURL;
private final Object syncObject = new Object();
/**
* Initial Axis Server reader setup.
*/
private void init()
{
ImageIO.setUseCache( false );
imageDecoder = ( ImageReader ) ImageIO.getImageReadersByFormatName( "JPEG" ).next();
}
/**
* AxisServerConnection Constructor.
*
* @param url
* URL of Axis Server device
* @throws MalformedURLException
*/
public AxisServerConnection( final String url ) throws MalformedURLException
{
this.url = new URL( url );
init();
}
/**
* Returns the Axis Server device URL.
*
* @return url URL of Axis Server device
*/
public URL getUrl()
{
return url;
}
/**
* The act of setting the url could initiate a disconnect. I'm leaving this
* alone for now in case a situation arises where you want to change the URL
* but video should still be coming out from the old location. This should
* allow for more flexibility.
*
* @param url
* URL of Axis Server device
*/
public void setUrl( final URL url )
{
this.url = url;
}
/**
* Sets the Axis Server device URL.
*
* @param url
* URL of Axis Server device
* @throws MalformedURLException
*/
public void setUrl( final String url ) throws MalformedURLException
{
this.url = new URL( url );
}
/**
* HTTP Connect to the Axis Server device.
*
* @param fps
* Frames per second
* @param vidDim
* Selected video dimension to size screen to
* @throws MalformedURLException
*/
public synchronized void connect() throws MalformedURLException
{
synchronized ( syncObject )
{
logger.debug( "Connected " + connected + " url " + url + " connectedURL " + connectedURL );
// DE3423 : Reconnect if not already connected or if URL has changed and
// hence new connection should be established
if ( url != null )
{
if ( !connected || !url.toString().equals( connectedURL ) )
{
try
{
logger.trace( "Connecting to AxisServer at URL[" + url + "]" );
huc = ( HttpURLConnection ) url.openConnection();
if( huc.getUseCaches() )
{
huc.setUseCaches( false );
}
logger.debug( "useCache is :" + huc.getUseCaches() + " for url: " + url );
dis = new DataInputStream( new BufferedInputStream( huc.getInputStream() ) );
iis = ImageIO.createImageInputStream( dis );
connected = true;
connectedURL = url.toString();
}
catch ( Exception e )
{
logger.warn( "Exception caught during establishing connection to " + url, e );
disconnect();
}
}
}
}
}
/**
* HTTP Disconnect from the Axis Server Device.
*/
public synchronized void disconnect()
{
synchronized ( syncObject )
{
try
{
if ( connected )
{
logger.debug( "Disconnecting: Currently connected? " + connected );
connected = false;
if ( dis != null )
{
dis.close();
}
if ( iis != null )
{
iis.close();
}
if ( huc != null )
{
huc.disconnect();
}
dis = null;
iis = null;
}
}
catch ( Exception e )
{
logger.debug( "Axis Server not disconnected properly " + e.getMessage() );
e = null;
}
}
}
/**
* Reads a JPEG image from the Axis Server device.
*
* @throws IOException
*/
private synchronized void readJPG() throws IOException
{
synchronized ( syncObject )
{
if ( !connected )
{
return;
}
imageDecoder.setInput( iis, true );
boolean jpegFound = false;
boolean contentTypeFound = false;
String line;
int retryCount=0;
do
{
//logger.info("readJPEG on :"+url+"retry count:"+retryCount);
retryCount++;
line = iis.readLine();
if(line != null){
if ( line.indexOf( "Content-" ) > -1 )
{
//logger.debug(" Read the line:"+line +"retry count"+retryCount);
contentTypeFound = true;
}
else if ( ( contentTypeFound ) && ( line.length() == 0 ) )
{
jpegFound = true;
}
}
/*
* bemman01c: I still dont understand why this do while is put here.
* But if i remove this i get an exception from the ImageDecoder.
* ===================================================================
* else if ( ( contentTypeFound ) && ( line.length() == 0 ) )
* {
* jpegFound = true;
* }
* why is the length check doing an equal to 0 check and deciding it is jpeg.
*
* Adding a break condition so that this does not go into an infinite loop.
* What i found was the Content-length or Content-imagetype strings comes in the 3rd
* or 4th retry. Putting a bigger value 20 just to be on the safer side.
*/
if(retryCount>20){
logger.error("Reading jpeg from Axis url failed. URL:"+url);
throw new IOException("Reading jpeg from Axis url failed.URL:"+url);
}
} while ( !jpegFound && connected );
image = imageDecoder.read( 0 );
}
}
/**
* Returns Axis Server device connection status true or false.
*
* @return connected connection status to Axis Server device
*/
public boolean isConnected()
{
return connected;
}
/**
* Returns a snapshot JPEG image captured from the Axis server Device.
*
* @return image JPEG image
* @throws IOException
*/
public BufferedImage getImage() throws IOException
{
readJPG();
return image;
}
/**
* Returns an ArrayList (table) of available Axis Video Server screen
* resolutions. This is a list of a list which creates a multidemensioned
* List (table). [col1: Dimension id] [col2: Dimension w x h]
*
* @return ArrayList Arraylist of available screen dimensions.
*/
public static Collection< String > getAvailableVideoResolutions()
{
return AxisVideoSize.getVideoSizesAsString();
}
}