package org.red5.server.stream.consumer;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 by respective authors (see below). All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.apache.mina.common.ByteBuffer;
import org.red5.io.IStreamableFile;
import org.red5.io.IStreamableFileFactory;
import org.red5.io.IStreamableFileService;
import org.red5.io.ITag;
import org.red5.io.ITagWriter;
import org.red5.io.StreamableFileFactory;
import org.red5.io.flv.impl.FLVReader;
import org.red5.io.flv.impl.Tag;
import org.red5.server.api.IScope;
import org.red5.server.api.ScopeUtils;
import org.red5.server.api.stream.IClientStream;
import org.red5.server.messaging.IMessage;
import org.red5.server.messaging.IMessageComponent;
import org.red5.server.messaging.IPipe;
import org.red5.server.messaging.IPipeConnectionListener;
import org.red5.server.messaging.IPushableConsumer;
import org.red5.server.messaging.OOBControlMessage;
import org.red5.server.messaging.PipeConnectionEvent;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.FlexStreamSend;
import org.red5.server.net.rtmp.message.Constants;
import org.red5.server.stream.IStreamData;
import org.red5.server.stream.message.RTMPMessage;
import org.red5.server.stream.message.ResetMessage;
import org.red5.server.stream.message.StatusMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Consumer that pushes messages to file. Used when recording live streams.
*/
public class FileConsumer implements Constants, IPushableConsumer,
IPipeConnectionListener {
/**
* Logger
*/
private static final Logger log = LoggerFactory.getLogger(FileConsumer.class);
/**
* Scope
*/
private IScope scope;
/**
* File
*/
private File file;
/**
* Tag writer
*/
private ITagWriter writer;
/**
* Operation mode
*/
private String mode;
/**
* Offset
*/
private int offset;
/**
* Last write timestamp
*/
private int lastTimestamp;
/**
* Start timestamp
*/
private int startTimestamp;
/**
* Creates file consumer
* @param scope Scope of consumer
* @param file File
*/
public FileConsumer(IScope scope, File file) {
this.scope = scope;
this.file = file;
startTimestamp = -1;
// Get the duration from the existing file
long duration = FLVReader.getDuration(file);
if (duration > 0) {
offset = (int) duration + 30;
}
}
/**
* Push message through pipe
* @param pipe Pipe
* @param message Message to push
* @throws IOException if message could not be written
*/
public synchronized void pushMessage(IPipe pipe, IMessage message) throws IOException {
if (message instanceof ResetMessage) {
startTimestamp = -1;
offset += lastTimestamp;
return;
} else if (message instanceof StatusMessage) {
return;
}
if (!(message instanceof RTMPMessage)) {
return;
}
if (writer == null) {
init();
}
RTMPMessage rtmpMsg = (RTMPMessage) message;
final IRTMPEvent msg = rtmpMsg.getBody();
if (startTimestamp == -1) {
startTimestamp = msg.getTimestamp();
}
// if we're dealing with a FlexStreamSend IRTMPEvent, this avoids relative timestamp calculations
int timestamp = msg.getTimestamp();
if (!(msg instanceof FlexStreamSend)){
timestamp -= startTimestamp;
lastTimestamp = timestamp;
}
if (timestamp < 0) {
log.warn("Skipping message with negative timestamp.");
return;
}
ITag tag = new Tag();
tag.setDataType(msg.getDataType());
//Always add offset since it's needed for "append" publish mode
//It adds on disk flv file duration
//Search for "offset" in this class constructor
tag.setTimestamp(timestamp + offset);
if (msg instanceof IStreamData) {
ByteBuffer data = ((IStreamData) msg).getData().asReadOnlyBuffer();
tag.setBodySize(data.limit());
tag.setBody(data);
}
try {
writer.writeTag(tag);
} catch (IOException e) {
log.error("error writing tag", e);
throw e;
}
}
/**
* Out-of-band control message handler
*
* @param source Source of message
* @param pipe Pipe that is used to transmit OOB message
* @param oobCtrlMsg OOB control message
*/
public synchronized void onOOBControlMessage(IMessageComponent source, IPipe pipe,
OOBControlMessage oobCtrlMsg) {
}
/**
* Pipe connection event handler
* @param event Pipe connection event
*/
public synchronized void onPipeConnectionEvent(PipeConnectionEvent event) {
switch (event.getType()) {
case PipeConnectionEvent.CONSUMER_CONNECT_PUSH:
if (event.getConsumer() != this) {
break;
}
Map<?, ?> paramMap = event.getParamMap();
if (paramMap != null) {
mode = (String) paramMap.get("mode");
}
break;
case PipeConnectionEvent.CONSUMER_DISCONNECT:
if (event.getConsumer() != this) {
break;
}
case PipeConnectionEvent.PROVIDER_DISCONNECT:
// we only support one provider at a time
// so do releasing when provider disconnects
uninit();
break;
default:
break;
}
}
/**
* Initialization
*
* @throws IOException I/O exception
*/
private void init() throws IOException {
IStreamableFileFactory factory = (IStreamableFileFactory) ScopeUtils
.getScopeService(scope, IStreamableFileFactory.class,
StreamableFileFactory.class);
File folder = file.getParentFile();
if (!folder.exists())
if (!folder.mkdirs())
throw new IOException("Could not create parent folder");
if (!file.isFile()) {
// Maybe the (previously existing) file has been deleted
file.createNewFile();
} else if (!file.canWrite()) {
throw new IOException("The file is read-only");
}
IStreamableFileService service = factory.getService(file);
IStreamableFile flv = service.getStreamableFile(file);
if (mode == null || mode.equals(IClientStream.MODE_RECORD)) {
writer = flv.getWriter();
} else if (mode.equals(IClientStream.MODE_APPEND)) {
writer = flv.getAppendWriter();
} else {
throw new IllegalStateException("Illegal mode type: " + mode);
}
}
/**
* Reset
*/
synchronized private void uninit() {
if (writer != null) {
writer.close();
writer = null;
}
}
}