/*
Milenia Grafter Server
Copyright (c) 2007-2008 by Milan Toth. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
of the License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.milgra.server;
/**
StreamReader class
@mail milgra@milgra.com
@author Milan Toth
@version 20080315
**/
import com.milgra.server.encoder.RtmpFactory;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
/*
Tasks of Streamreader
- Analyze flv stream, add frame info to buffers
- Set position on request
- Set speed on request
- Dispatch frames on steps
*/
public class StreamReader extends OProcess
{
// name - stream/file name
// subscriber - player/router
// streamFile - streamfile
public String name;
public OStream subscriber;
public RandomAccessFile streamFile;
// measures for stepping
public long parttime;
public long lasttime;
// seektime - last seek time
// multiply - speed multiplier
// position - playhead position
// duration - stream duration
public double seektime;
public double multiply;
public double position;
public double duration;
public double newPosition;
public double newMultiply;
// close - reader is closing
// paused - paused
public boolean close;
public boolean paused;
public boolean change;
// frame buffers
public FrameBuffer audioBuffer;
public FrameBuffer videoBuffer;
// packet containers
public ArrayList < RtmpPacket > audioPackets;
public ArrayList < RtmpPacket > videoPackets;
/**
* Steam Reader constructor
* @param fileNameX
* @param subscriberX
**/
public StreamReader ( String fileNameX , OStream subscriberX )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.construct " + fileNameX + " " + subscriberX );
// create
try
{
streamFile = new RandomAccessFile( new File( Library.STREAMDIR , fileNameX ) , "r" );
}
catch ( IOException exception ) { exception.printStackTrace( ); }
audioBuffer = new FrameBuffer( 0x08 , streamFile );
videoBuffer = new FrameBuffer( 0x09 , streamFile );
audioPackets = new ArrayList < RtmpPacket > ( );
videoPackets = new ArrayList < RtmpPacket > ( );
// set
name = fileNameX;
subscriber = subscriberX;
parttime = 0;
lasttime = 0;
seektime = 0;
position = 0;
duration = 0;
multiply = 1;
// start
analyze( );
start( );
}
/**
* StreamReader destructor
**/
public void destruct ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.destruct" );
try
{
// close file
streamFile.close( );
// close buffers
audioBuffer.close( );
videoBuffer.close( );
Server.unregisterProcess( this , "stream" );
}
catch ( IOException exception ) { exception.printStackTrace( ); }
}
/**
* StreamReader.close
**/
public void close ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.close" );
close = true;
}
/**
* Analyzes stream, inits buffers
**/
public void analyze ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.createIndex " );
try
{
int bodyType;
int bodySize;
int flvStamp;
// skip starting "FLV" and type
long position = 9;
do
{
// skin previous size
position += 4;
streamFile.seek( position );
// reading first chunk and flv type
bodyType = streamFile.read( );
// if not end of file
if ( bodyType != -1 )
{
// read size and timestamp
bodySize = streamFile.read( ) << 16 | streamFile.read( ) << 8 | streamFile.read( );
flvStamp = streamFile.read( ) << 16 | streamFile.read( ) << 8 | streamFile.read( );
// skip size, stamp and streamid
position += 11;
// fill up buffer
if ( bodyType == 0x08 ) audioBuffer.registerFrame( position , flvStamp , bodySize);
if ( bodyType == 0x09 ) videoBuffer.registerFrame( position , flvStamp , bodySize);
// skip body
position += bodySize;
duration = flvStamp;
}
}
while ( bodyType != -1 );
}
catch ( IOException exception ) { exception.printStackTrace( ); }
}
/**
* Reader step
**/
public void step ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.step " + position );
if ( change )
{
multiply = newMultiply;
position = newPosition;
seektime = newPosition;
audioBuffer.speed( multiply );
videoBuffer.speed( multiply );
audioBuffer.seek( position );
videoBuffer.seek( position );
audioBuffer.step( );
videoBuffer.step( );
change = false;
sendSeek( );
}
// calculate elapsed time
parttime = System.currentTimeMillis( ) - lasttime;
lasttime = System.currentTimeMillis( );
position = position + parttime * multiply;
if ( position <= duration && position >= 0 )
{
// get packets
audioBuffer.giveFrames( position , audioPackets );
videoBuffer.giveFrames( position , videoPackets );
for ( RtmpPacket packet : audioPackets )
{
packet.flvStamp /= multiply;
subscriber.take( packet );
if ( Math.abs( multiply ) > 1 ) break;
}
for ( RtmpPacket packet : videoPackets )
{
if ( seektime != 0 )
{
packet.first = true;
packet.flvStamp = ( int ) seektime;
// set it to keyframe
packet.body[ 0 ] = ( byte ) ( packet.body[ 0 ] & 0x0F );
packet.body[ 0 ] = ( byte ) ( packet.body[ 0 ] | 0x10 );
seektime = 0;
subscriber.take( packet );
}
else
{
packet.flvStamp /= multiply;
subscriber.take( packet );
if ( Math.abs( multiply ) > 1 ) break;
}
}
audioPackets.clear( );
videoPackets.clear( );
}
else stop( );
if ( close ) destruct( );
}
/**
* Starts playing
**/
public void start ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.start" );
lasttime = System.currentTimeMillis( );
audioBuffer.step( );
videoBuffer.step( );
audioBuffer.start( );
videoBuffer.start( );
sendStart( );
Server.registerProcess( this , "stream" );
}
/**
* Stops playing
**/
public void stop ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.stop" );
audioBuffer.stop( );
videoBuffer.stop( );
sendStop( );
Server.unregisterProcess( this , "stream" );
}
/**
* Pauses reading
*/
public void pause ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.pause" );
if ( paused ) sendUnpause( );
if ( paused ) Server.registerProcess( this , "stream" );
else Server.unregisterProcess( this , "stream" );
paused = !paused;
}
/**
* Sets reading speed
* @param multiplierX speed multiplier
**/
public void setSpeed ( double multiplyX )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.setSpeed " + multiplyX );
if ( multiplyX > 5 ) multiplyX = 5; else
if ( multiplyX < -5 ) multiplyX = -5; else
if ( multiplyX == 0 ) multiplyX = 1;
newMultiply = multiplyX;
newPosition = position;
change = true;
}
/**
* Sets playhead position
* @param positionX
*/
public void setPosition ( double positionX )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.setPosition " + positionX );
if ( positionX < 0 ) positionX = 0; else
if ( positionX > duration ) positionX = duration;
newMultiply = multiply;
newPosition = positionX;
change = true;
}
/**
* Sends start related rtmp messages
*/
public void sendStart ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.sendStart " );
// reset stream
subscriber.take( RtmpFactory.streamControl( 4 , subscriber.getId( ) ) );
// reset playhead
subscriber.take( RtmpFactory.streamControl( 0 , subscriber.getId( ) ) );
// reset status
subscriber.take( RtmpFactory.playReset( subscriber.getClientId( ) , name ) );
// start status
subscriber.take( RtmpFactory.playStart( subscriber.getClientId( ) , name ) );
// sample access
subscriber.take( RtmpFactory.sampleAccess( true , true ) );
// data start for playhead
subscriber.take( RtmpFactory.dataStart( ) );
// duration
subscriber.take( RtmpFactory.streamDuration( duration / Math.abs( multiply ) ) );
}
/**
* Send stop related rtmp message
*/
public void sendStop ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.sendStop " );
subscriber.take( RtmpFactory.playStop( subscriber.getClientId( ) , name ));
}
/**
* Sends unpause related messages
*/
public void sendUnpause ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.sendUnpause " );
subscriber.take( RtmpFactory.playStart( subscriber.getClientId( ) , name ) );
subscriber.take( RtmpFactory.sampleAccess( true , true ) );
subscriber.take( RtmpFactory.dataStart( ) );
}
/**
* Sends seek related rtmp messages
*/
public void sendSeek ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.sendSeek " );
// clear buffer
subscriber.take( RtmpFactory.streamControl( 1 , subscriber.getId( ) ) );
// chunk size
subscriber.take( RtmpFactory.chunk( 4096 ) );
// reset stream
subscriber.take( RtmpFactory.streamControl( 4 , subscriber.getId( ) ) );
// reset playhead
subscriber.take( RtmpFactory.streamControl( 0 , subscriber.getId( ) ) );
// seek notify
subscriber.take( RtmpFactory.seekNotify( ( int ) position , subscriber.getId( ) , subscriber.getClientId( ) , name ) );
// start notify
subscriber.take( RtmpFactory.playStart( subscriber.getId( ) , name ) );
// sample access before data
subscriber.take( RtmpFactory.sampleAccess( true , true ) );
// data start for playhead
subscriber.take( RtmpFactory.dataStart( ) );
// empty audio frame for playhead sync
subscriber.take( RtmpFactory.emptyFrame( 0x08 , ( int ) position ) );
}
/**
* Returns reading speed
* @return speed multiplier
**/
public double getSpeed ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.getSpeed" );
return multiply;
}
/**
* Returns playhead position in seconds
* @return
*/
public double getPosition ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.getPosition " );
return position / 1000 * multiply;
}
/**
* Returns duration
* @return duration
**/
public double getDuration ( )
{
// System.out.println( System.currentTimeMillis( ) + " StreamReader.duration" );
return duration / 1000;
}
// non used interface functions
public void subscribe ( ) { }
public void unsubscribe ( ) { }
}