blob: fa7bd5917c868036be49713d1da2b5652b6e88b1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.remote;
import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.openmeetings.data.flvrecord.converter.FlvInterviewConverterTask;
import org.apache.openmeetings.data.flvrecord.converter.FlvInterviewReConverterTask;
import org.apache.openmeetings.data.flvrecord.converter.FlvRecorderConverterTask;
import org.apache.openmeetings.data.flvrecord.listener.StreamListener;
import org.apache.openmeetings.data.user.UserManager;
import org.apache.openmeetings.db.dao.record.FlvRecordingDao;
import org.apache.openmeetings.db.dao.record.FlvRecordingLogDao;
import org.apache.openmeetings.db.dao.record.FlvRecordingMetaDataDao;
import org.apache.openmeetings.db.dao.record.FlvRecordingMetaDeltaDao;
import org.apache.openmeetings.db.dao.room.RoomDao;
import org.apache.openmeetings.db.dao.server.ISessionManager;
import org.apache.openmeetings.db.dao.server.SessiondataDao;
import org.apache.openmeetings.db.dao.user.UserDao;
import org.apache.openmeetings.db.entity.record.FlvRecording;
import org.apache.openmeetings.db.entity.record.FlvRecordingMetaData;
import org.apache.openmeetings.db.entity.record.FlvRecordingMetaData.Status;
import org.apache.openmeetings.db.entity.room.Client;
import org.apache.openmeetings.remote.red5.ScopeApplicationAdapter;
import org.apache.openmeetings.util.AuthLevelUtil;
import org.apache.openmeetings.util.CalendarPatterns;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.stream.ClientBroadcastStream;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
public class FLVRecorderService implements IPendingServiceCallback {
private static final Logger log = Red5LoggerFactory.getLogger(FLVRecorderService.class, webAppRootKey);
/**
* Stores a reference to all available listeners we need that reference, as the internal references stored with the
* red5 stream object might be gone when the user closes the browser. But each listener has an asynchronous
* component that needs to be closed no matter how the user leaves the application!
*/
private static final Map<Long, StreamListener> streamListeners = new ConcurrentHashMap<Long, StreamListener>();
// Spring Beans
@Autowired
private ISessionManager sessionManager;
@Autowired
private FlvRecordingDao flvRecordingDaoImpl;
@Autowired
private UserDao usersDaoImpl;
@Autowired
private RoomDao roomDao;
@Autowired
private FlvRecorderConverterTask flvRecorderConverterTask;
@Autowired
private FlvInterviewConverterTask flvInterviewConverterTask;
@Autowired
private FlvInterviewReConverterTask flvInterviewReConverterTask;
@Autowired
private FlvRecordingLogDao flvRecordingLogDaoImpl;
@Autowired
private SessiondataDao sessiondataDao;
@Autowired
private UserManager userManager;
@Autowired
private ScopeApplicationAdapter scopeApplicationAdapter;
@Autowired
private FlvRecordingMetaDeltaDao metaDeltaDao;
@Autowired
private FlvRecordingMetaDataDao metaDataDao;
public void resultReceived(IPendingServiceCall arg0) {
}
public Client checkForRecording() {
try {
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
log.debug("getCurrentRoomClient -2- " + streamid);
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
for (Client rcl : sessionManager.getClientListByRoom(currentClient.getRoom_id())) {
if (rcl.getIsRecording()) {
return rcl;
}
}
return null;
} catch (Exception err) {
log.error("[checkForRecording]", err);
}
return null;
}
private static String generateFileName(Long flvRecording_id, String streamid) throws Exception {
String dateString = CalendarPatterns.getTimeForStreamId(new Date());
return "rec_" + flvRecording_id + "_stream_" + streamid + "_" + dateString;
}
public String recordMeetingStream(String roomRecordingName, String comment, Boolean isInterview) {
try {
log.debug(":: recordMeetingStream ::");
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
Long room_id = currentClient.getRoom_id();
Date now = new Date();
// Receive flvRecordingId
Long flvRecordingId = flvRecordingDaoImpl.addFlvRecording("", roomRecordingName, null,
currentClient.getUser_id(), room_id, now, null, currentClient.getUser_id(), comment,
currentClient.getStreamid(), currentClient.getVWidth(), currentClient.getVHeight(), isInterview);
// Update Client and set Flag
currentClient.setIsRecording(true);
currentClient.setFlvRecordingId(flvRecordingId);
sessionManager.updateClientByStreamId(current.getClient().getId(), currentClient, false, null);
// get all stream and start recording them
for (Set<IConnection> conset : current.getScope().getConnections()) {
for (IConnection conn : conset) {
if (conn != null) {
if (conn instanceof IServiceCapableConnection) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
// Send every user a notification that the recording did start
if (!rcl.getIsAVClient()) {
((IServiceCapableConnection) conn).invoke("startedRecording", new Object[] { currentClient }, this);
}
// If its the recording client we need another type of Meta Data
if (rcl.getIsScreenClient()) {
if (rcl.getFlvRecordingId() != null && rcl.isScreenPublishStarted()) {
String streamName_Screen = generateFileName(flvRecordingId, rcl.getStreamPublishName().toString());
Long flvRecordingMetaDataId = metaDataDao.addFlvRecordingMetaData(
flvRecordingId, rcl.getFirstname() + " " + rcl.getLastname(), now, false,
false, true, streamName_Screen, rcl.getInterviewPodId());
// Start FLV Recording
recordShow(conn, rcl.getStreamPublishName(), streamName_Screen, flvRecordingMetaDataId, true, isInterview);
// Add Meta Data
rcl.setFlvRecordingMetaDataId(flvRecordingMetaDataId);
sessionManager.updateClientByStreamId(rcl.getStreamid(), rcl, false, null);
}
} else if (rcl.getIsAVClient()
&& (rcl.getAvsettings().equals("av") || rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v"))) {
// if the user does publish av, a, v
// But we only record av or a, video only is not interesting
String streamName = generateFileName(flvRecordingId, String.valueOf(rcl.getBroadCastID()).toString());
// Add Meta Data
boolean isAudioOnly = false;
if (rcl.getAvsettings().equals("a")) {
isAudioOnly = true;
}
boolean isVideoOnly = false;
if (rcl.getAvsettings().equals("v")) {
isVideoOnly = true;
}
Long flvRecordingMetaDataId = metaDataDao.addFlvRecordingMetaData(flvRecordingId,
rcl.getFirstname() + " " + rcl.getLastname(), now, isAudioOnly, isVideoOnly, false, streamName,
rcl.getInterviewPodId());
rcl.setFlvRecordingMetaDataId(flvRecordingMetaDataId);
sessionManager.updateClientByStreamId(rcl.getStreamid(), rcl, false, null);
// Start FLV recording
recordShow(conn, String.valueOf(rcl.getBroadCastID()).toString(), streamName, flvRecordingMetaDataId, !isAudioOnly,
isInterview);
}
}
}
}
}
return roomRecordingName;
} catch (Exception err) {
log.error("[recordMeetingStream]", err);
}
return null;
}
/**
* Start recording the published stream for the specified broadcast-Id
*
* @param conn
* @param broadcastid
* @param streamName
* @param flvRecordingMetaDataId
* @throws Exception
*/
private synchronized void recordShow(IConnection conn, String broadcastid, String streamName, Long flvRecordingMetaDataId,
boolean isScreenData, Boolean isInterview) throws Exception {
try {
log.debug("Recording show for: " + conn.getScope().getContextPath());
log.debug("Name of CLient and Stream to be recorded: " + broadcastid);
// log.debug("Application.getInstance()"+Application.getInstance());
log.debug("Scope " + conn);
log.debug("Scope " + conn.getScope());
// Get a reference to the current broadcast stream.
ClientBroadcastStream stream = (ClientBroadcastStream) scopeApplicationAdapter.getBroadcastStream(conn.getScope(), broadcastid);
if (stream == null) {
log.debug("Unable to get stream: " + streamName);
return;
}
// Save the stream to disk.
log.debug("### stream " + stream);
log.debug("### streamName " + streamName);
log.debug("### conn.getScope() " + conn.getScope());
log.debug("### flvRecordingMetaDataId " + flvRecordingMetaDataId);
log.debug("### isScreenData " + isScreenData);
log.debug("### isInterview " + isInterview);
StreamListener streamListener = new StreamListener(!isScreenData, streamName, conn.getScope(),
flvRecordingMetaDataId, isScreenData, isInterview, metaDataDao, metaDeltaDao);
streamListeners.put(flvRecordingMetaDataId, streamListener);
stream.addStreamListener(streamListener);
// Just for Debug Purpose
// stream.saveAs(streamName+"_DEBUG", false);
} catch (Exception e) {
log.error("Error while saving stream: " + streamName, e);
}
}
/**
* Stops recording the publishing stream for the specified IConnection.
*
* @param conn
*/
public synchronized void stopRecordingShow(IConnection conn, String broadcastId, Long flvRecordingMetaDataId) {
try {
if (flvRecordingMetaDataId == null) {
// this should be fixed, can be useful for debugging, after all this is an error
// but we don't want the application to completely stop the process
log.error("flvRecordingMetaDataId is null");
}
log.debug("** stopRecordingShow: " + conn);
log.debug("### Stop recording show for broadcastId: " + broadcastId + " || " + conn.getScope().getContextPath());
Object streamToClose = scopeApplicationAdapter.getBroadcastStream(conn.getScope(), broadcastId);
StreamListener listenerAdapter = streamListeners.get(flvRecordingMetaDataId);
log.debug("Stream Closing :: " + flvRecordingMetaDataId);
ClientBroadcastStream stream = (ClientBroadcastStream) streamToClose;
// the stream can be null if the user just closes the browser
// without canceling the recording before leaving
if (stream != null) {
// Iterate through all stream listeners and stop the appropriate
if (stream.getStreamListeners() != null) {
for (IStreamListener iStreamListener : stream.getStreamListeners()) {
stream.removeStreamListener(iStreamListener);
}
}
}
if (listenerAdapter == null) {
log.debug("Stream Not Found :: " + flvRecordingMetaDataId);
log.debug("Available Streams :: " + streamListeners.size());
for (Long entryKey : streamListeners.keySet()) {
log.debug("Stored flvRecordingMetaDataId in Map: " + entryKey);
}
// Manually call finish on the stream so that there is no endless loop waiting
// in the FlvRecorderConverter waiting for the stream to finish
// this would normally happen in the Listener
FlvRecordingMetaData metaData = metaDataDao.get(flvRecordingMetaDataId);
if (metaData.getStreamStatus() == Status.STARTED) {
metaData.setStreamStatus(Status.STOPPED);
metaDataDao.update(metaData);
}
throw new IllegalStateException("Could not find Listener to stop! flvRecordingMetaDataId " + flvRecordingMetaDataId);
} else {
FlvRecordingMetaData metaData = metaDataDao.get(flvRecordingMetaDataId);
metaData.setStreamStatus(Status.STOPPING);
metaDataDao.update(metaData);
}
listenerAdapter.closeStream();
streamListeners.remove(flvRecordingMetaDataId);
} catch (Exception err) {
log.error("[stopRecordingShow]", err);
}
}
public Long stopRecordAndSave(IScope scope, Client currentClient, Long storedFlvRecordingId) {
try {
log.debug("stopRecordAndSave " + currentClient.getUsername() + "," + currentClient.getUserip());
// get all stream and stop recording them
for (Set<IConnection> conset : scope.getConnections()) {
for (IConnection conn : conset) {
if (conn != null) {
if (conn instanceof IServiceCapableConnection) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl == null) {
continue;
}
log.debug("is this users still alive? stop it :" + rcl);
if (rcl.getIsScreenClient()) {
if (rcl.getFlvRecordingId() != null && rcl.isScreenPublishStarted()) {
// Stop FLV Recording
stopRecordingShow(conn, rcl.getStreamPublishName(), rcl.getFlvRecordingMetaDataId());
// Update Meta Data
metaDataDao.updateFlvRecordingMetaDataEndDate(rcl.getFlvRecordingMetaDataId(), new Date());
}
} else if (rcl.getIsAVClient()
&& (rcl.getAvsettings().equals("av") || rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v"))) {
stopRecordingShow(conn, String.valueOf(rcl.getBroadCastID()).toString(), rcl.getFlvRecordingMetaDataId());
// Update Meta Data
metaDataDao.updateFlvRecordingMetaDataEndDate(rcl.getFlvRecordingMetaDataId(), new Date());
}
}
}
}
}
// Store to database
Long flvRecordingId = currentClient.getFlvRecordingId();
// In the Case of an Interview the stopping client does not mean
// that its actually the recording client
if (storedFlvRecordingId != null) {
flvRecordingId = storedFlvRecordingId;
}
if (flvRecordingId != null) {
flvRecordingDaoImpl.updateFlvRecordingEndTime(flvRecordingId, new Date(), currentClient.getOrganization_id());
// Reset values
currentClient.setFlvRecordingId(null);
currentClient.setIsRecording(false);
sessionManager.updateClientByStreamId(currentClient.getStreamid(), currentClient, false, null);
log.debug("flvRecorderConverterTask ", flvRecorderConverterTask);
FlvRecording flvRecording = flvRecordingDaoImpl.get(flvRecordingId);
if (flvRecording.getIsInterview() == null || !flvRecording.getIsInterview()) {
flvRecorderConverterTask.startConversionThread(flvRecordingId);
} else {
flvInterviewConverterTask.startConversionThread(flvRecordingId);
}
}
} catch (Exception err) {
log.error("[-- stopRecordAndSave --]", err);
}
return new Long(-1);
}
public Client checkLzRecording() {
try {
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
log.debug("getCurrentRoomClient -2- " + streamid);
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
log.debug("getCurrentRoomClient -#########################- " + currentClient.getRoom_id());
for (Client rcl : sessionManager.getClientListByRoomAll(currentClient.getRoom_id())) {
if (rcl.getIsRecording()) {
return rcl;
}
}
} catch (Exception err) {
log.error("[checkLzRecording]", err);
}
return null;
}
public void stopRecordingShowForClient(IConnection conn, Client rcl) {
try {
// this cannot be handled here, as to stop a stream and to leave a
// room is not
// the same type of event.
// StreamService.addRoomClientEnterEventFunc(rcl, roomrecordingName,
// rcl.getUserip(), false);
log.debug("### stopRecordingShowForClient: " + rcl);
if (rcl.getIsScreenClient()) {
if (rcl.getFlvRecordingId() != null && rcl.isScreenPublishStarted()) {
// Stop FLV Recording
// FIXME: Is there really a need to stop it manually if the
// user just
// stops the stream?
stopRecordingShow(conn, rcl.getStreamPublishName(), rcl.getFlvRecordingMetaDataId());
// Update Meta Data
metaDataDao.updateFlvRecordingMetaDataEndDate(rcl.getFlvRecordingMetaDataId(), new Date());
}
} else if (rcl.getIsAVClient()
&& (rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v") || rcl.getAvsettings().equals("av"))) {
// FIXME: Is there really a need to stop it manually if the user
// just stops the stream?
stopRecordingShow(conn, String.valueOf(rcl.getBroadCastID()), rcl.getFlvRecordingMetaDataId());
// Update Meta Data
metaDataDao.updateFlvRecordingMetaDataEndDate(rcl.getFlvRecordingMetaDataId(), new Date());
}
} catch (Exception err) {
log.error("[stopRecordingShowForClient]", err);
}
}
public void addRecordingByStreamId(IConnection conn, String streamId, Client rcl, Long flvRecordingId) {
try {
FlvRecording flvRecording = flvRecordingDaoImpl.get(flvRecordingId);
Date now = new Date();
// If its the recording client we need another type of Meta Data
if (rcl.getIsScreenClient()) {
if (rcl.getFlvRecordingId() != null && rcl.isScreenPublishStarted()) {
String streamName_Screen = generateFileName(flvRecordingId, rcl.getStreamPublishName().toString());
log.debug("############## ADD SCREEN OF SHARER :: " + rcl.getStreamPublishName());
Long flvRecordingMetaDataId = metaDataDao.addFlvRecordingMetaData(flvRecordingId, rcl.getFirstname()
+ " " + rcl.getLastname(), now, false, false, true, streamName_Screen, rcl.getInterviewPodId());
// Start FLV Recording
recordShow(conn, rcl.getStreamPublishName(), streamName_Screen, flvRecordingMetaDataId, true,
flvRecording.getIsInterview());
// Add Meta Data
rcl.setFlvRecordingMetaDataId(flvRecordingMetaDataId);
sessionManager.updateClientByStreamId(rcl.getStreamid(), rcl, false, null);
}
} else if (rcl.getIsAVClient()
&& (rcl.getAvsettings().equals("av") || rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v"))) {
// if the user does publish av, a, v
// But we only record av or a, video only is not interesting
String streamName = generateFileName(flvRecordingId, String.valueOf(rcl.getBroadCastID()).toString());
// Add Meta Data
boolean isAudioOnly = false;
if (rcl.getAvsettings().equals("a")) {
isAudioOnly = true;
}
boolean isVideoOnly = false;
if (rcl.getAvsettings().equals("v")) {
isVideoOnly = true;
}
Long flvRecordingMetaDataId = metaDataDao.addFlvRecordingMetaData(flvRecordingId, rcl.getFirstname() + " "
+ rcl.getLastname(), now, isAudioOnly, isVideoOnly, false, streamName, rcl.getInterviewPodId());
// Start FLV recording
recordShow(conn, String.valueOf(rcl.getBroadCastID()).toString(), streamName, flvRecordingMetaDataId, false,
flvRecording.getIsInterview());
rcl.setFlvRecordingMetaDataId(flvRecordingMetaDataId);
sessionManager.updateClientByStreamId(rcl.getStreamid(), rcl, false, null);
}
} catch (Exception err) {
log.error("[addRecordingByStreamId]", err);
}
}
public Long restartConversion(String SID, Long flvRecordingId, Integer leftSideLoud, Integer rightSideLoud, Integer leftSideTime,
Integer rightSideTime) {
try {
Long users_id = sessiondataDao.checkSession(SID);
Long user_level = userManager.getUserLevelByID(users_id);
if (AuthLevelUtil.checkUserLevel(user_level)) {
log.debug("updateFileOrFolderName " + flvRecordingId);
FlvRecording flvRecording = flvRecordingDaoImpl.get(flvRecordingId);
flvRecording.setPreviewImage(null);
flvRecording.setProgressPostProcessing(0);
flvRecordingDaoImpl.updateFlvRecording(flvRecording);
if (flvRecording.getIsInterview() == null || !flvRecording.getIsInterview()) {
flvRecorderConverterTask.startConversionThread(flvRecordingId);
} else {
flvInterviewReConverterTask.startConversionThread(flvRecordingId, leftSideLoud, rightSideLoud, leftSideTime,
rightSideTime);
}
}
} catch (Exception err) {
log.error("[restartInterviewConversion] ", err);
}
return null;
}
}