/** * 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.recorder; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.comcast.cats.info.VideoRecorderServiceConstants; import com.comcast.cats.info.VideoRecorderState; import com.comcast.cats.info.VideoRecordingOptions; import com.comcast.cats.recorder.domain.Recording; import com.comcast.cats.recorder.domain.RecordingStatus; import com.comcast.cats.recorder.exception.RecorderNotFoundException; import com.comcast.cats.recorder.exception.VideoRecorderConnectionException; import com.comcast.cats.recorder.exception.VideoRecorderException; import com.comcast.cats.recorder.exception.VideoRecorderInstantiationException; import com.comcast.cats.service.util.ApplicationConfigUtil; import com.comcast.cats.service.util.AssertUtil; /** * Default implementation of {@link VideoRecordingHandler} * * @author ssugun00c * @see VideoRecordingErrorHandler * */ public class DefaultVideoRecordingHandler extends ConcurrentHashMap< Integer, VideoRecorderTask > implements VideoRecordingHandler, VideoRecordingErrorHandler { private static List< Integer > activeTelnetPortList = new LinkedList< Integer >(); private static List< Integer > recentlyUsedTelnetPortList = new LinkedList< Integer >(); private static final long serialVersionUID = 1L; transient protected Logger logger = LoggerFactory.getLogger( getClass() ); // We need to support 16 concurrent recording private static final int POOL_SIZE = 20; transient private final ExecutorService pool = Executors.newFixedThreadPool( POOL_SIZE ); @Override public void submitRecording( Recording recording ) throws VideoRecorderInstantiationException { AssertUtil.isNull( "recorderId cannot be null", recording.getId() ); AssertUtil.isNullOrEmpty( "mrl cannot be null or empty", recording.getMrl() ); // Get the latest file in the list String filePath = recording.getMediaInfoEntityList().get( recording.getMediaInfoEntityList().size() - 1 ) .getFilePath(); logger.info( "Start record request receieved for videoRecorderId [" + recording.getId() + "][" + recording.getMrl() + "][" + filePath + "]" ); VideoRecorderTask videoRecorderTask = getVideoRecorder( recording, filePath, new VideoRecordingOptions() ); logger.info( "Submiting videoRecorderTask [" + videoRecorderTask + "]" ); submitRecording( recording, videoRecorderTask ); } private VideoRecorderTask getVideoRecorder( Recording recording, String filePath, VideoRecordingOptions videoRecordingOptions ) throws VideoRecorderInstantiationException { DefaultVideoRecorderTask videoRecorderTask = ( DefaultVideoRecorderTask ) get( recording.getId() ); if ( null == videoRecorderTask ) { if ( size() > VideoRecorderServiceConstants.MAX_CONCURRENT_RECORDING ) { throw new VideoRecorderInstantiationException( "Can't create new VideoRecorder. Max limit of [" + VideoRecorderServiceConstants.MAX_CONCURRENT_RECORDING + "] reached" ); } else { if ( null == videoRecordingOptions ) { videoRecordingOptions = new VideoRecordingOptions(); logger.info( "Using default videoRecordingOptions [" + videoRecordingOptions + "] for videoRecorderId [" + recording.getId() + "]" ); } synchronized ( this ) { videoRecorderTask = new DefaultVideoRecorderTask( recording, filePath, videoRecordingOptions, this ); int telnetport = getTelnetPort(); videoRecorderTask.setTelnetPort( telnetport ); videoRecorderTask.setCommand( getVlcCommand( recording.getMrl(), filePath, telnetport ) ); put( recording.getId(), videoRecorderTask ); } } } return videoRecorderTask; } private synchronized String getVlcCommand( String mrl, String filePath, int telnetport ) { String command = System.getProperty( VideoRecorderServiceConstants.SYSTEM_PROPERTY_VLC_EXECUTABLE_PATH ) + "vlc -I dummy " + mrl + " --sout file/" + VideoRecorderServiceConstants.DEFAULT_EXTENSION + ":" + filePath + " --extraintf=telnet --telnet-password " + getTelnetPassword() + " --telnet-port " + telnetport; logger.info( "[VLC command][" + command + "]" ); return command; } private String getTelnetPassword() { return System.getProperty( VideoRecorderServiceConstants.SYSTEM_PROPERTY_VLC_TELNET_PASSWORD ); } private synchronized int getTelnetPort() { int port = ApplicationConfigUtil.getTelnetPortRangeStart(); if ( getMax( activeTelnetPortList ) >= ApplicationConfigUtil.getTelnetPortRangeEnd() ) { cleanupRecentlyUsedTelnetPortList(); } while ( activeTelnetPortList.contains( port ) || recentlyUsedTelnetPortList.contains( port ) ) { port++; } activeTelnetPortList.add( port ); logger.info( "Calculated Telnet port [" + port + "]. Current list of active ports " + activeTelnetPortList ); return port; } private void submitRecording( Recording recording, VideoRecorderTask videoRecorderTask ) throws VideoRecorderInstantiationException { final String videoRecorderState = videoRecorderTask.getRecordingStatus().getState(); if ( ( null != videoRecorderState ) && ( VideoRecorderState.INITIALIZING.toString().equalsIgnoreCase( videoRecorderState ) ) ) { logger.info( "Submiting start record task for videoRecorderId [" + recording.getId() + "]" ); pool.submit( videoRecorderTask ); } else { throw new VideoRecorderInstantiationException( "Cannot Submit video record for [" + recording.getStbMacAddress() + "][" + recording.getStbMacAddress() + "][" + recording.getVideoServerPort() + "]. An existing recorder found with state : " + videoRecorderState + " videoRecorderId is [" + recording.getId() + "]" ); } } public void stopRecording( Integer recordingId ) throws VideoRecorderException, VideoRecorderConnectionException { AssertUtil.isNull( "recorderId cannot be null", recordingId ); logger.info( "Stop record request receieved for videoRecorderId [" + recordingId + "]" ); VideoRecorderTask videoRecorderTask = get( recordingId ); if ( null == videoRecorderTask ) { throw new VideoRecorderException( "No active recorder found for videoRecorderId [" + recordingId + "]." + "The record operation may already completed." ); } logger.info( "Submiting stop record task for videoRecorderId [" + recordingId + "] with telnet port [" + videoRecorderTask.getTelnetPort() + "]" ); // Stop recording videoRecorderTask.stopRecording(); logger.info( "Removing telnet port [" + videoRecorderTask.getTelnetPort() + "]" ); // This block will never execute if // videoRecorderTask.stopRecording() // fails. synchronized ( this ) { activeTelnetPortList.remove( Integer.valueOf( videoRecorderTask.getTelnetPort() ) ); recentlyUsedTelnetPortList.add( Integer.valueOf( videoRecorderTask.getTelnetPort() ) ); logger.info( "Complted removing telnet port. Current list of active ports [" + activeTelnetPortList + "]" ); remove( recordingId ); } } private void cleanupRecentlyUsedTelnetPortList() { logger.info( "Cleaning up recently used Telnet ports" ); while ( !recentlyUsedTelnetPortList.isEmpty() ) { recentlyUsedTelnetPortList.remove( 0 ); } } private int getMax( List< Integer > telnetPortList ) { int max = 0; for ( int port : telnetPortList ) { if ( port > max ) { max = port; } } return max; } @Override public RecordingStatus getRecordingStatus( Integer recordingId ) throws VideoRecorderException { AssertUtil.isNull( "recorderId cannot be null", recordingId ); logger.trace( "Get status request receieved for videoRecorderId [" + recordingId + "]" ); VideoRecorderTask videoRecorderTask = get( recordingId ); if ( null == videoRecorderTask ) { throw new RecorderNotFoundException( "No active recorder found for videoRecorderId [" + recordingId + "]." + "The record operation may already completed." ); } return videoRecorderTask.getRecordingStatus(); } // FIXME @Override public void handleError( Recording recording, String errorMessage ) { logger.info( "[HANDLE-ERROR][" + recording + "][" + errorMessage + "]" ); throw new UnsupportedOperationException( "handleError is not supported yet." ); } public static List< Integer > getActiveTelnetPortList() { return activeTelnetPortList; } public static List< Integer > getRecentlyUsedTelnetPortList() { return recentlyUsedTelnetPortList; } }