We are utilizing Adobe FMS 4.5 to record video from users webcam. We are not live-broadcasting this video, we just want to capture it and save it on the server to do things with it later (attach to users' account, show as in-page content, etc).
We've written a robust capture application that streams up to the server, unpublishes correctly, and allows the user to view the video after it's finished unpublishing, etc. This does seem to work, in that it saves video files as we expect.
The problem is that those recorded videos seem to have a small gap at the beginning. It's considerably less than one second. We're talking maybe 5/100s to 1/10th of a second that has no video data. It really looks like there is no video keyframe at the very beginning of the video.
Now, this wouldn't normally be an issue - the video just begins a bit into the playback, and plays fine in most players. Problem is, we're allowing the users to trim videos arbitrarily later on with a different tool - a custom visual interface to FFmpeg. If they begin a video trim before that first keyframe there's an ugly grey mess in the final output because there is no visual data in the area where FFmpeg started cutting.
My first thought was, "Oh, I must need to attach the camera (differently | at a different point | on some callback | etc)". I played around with attaching the camera before calling publish, after NetStream.Publish.Start, etc, etc.
Am I missing something intrinsic, here? Or am I just working with the wrong idea, or do I misunderstand the process?
Of course, the answer I completely expect, but don't want is, "That's just how FMS does it." :) We could add a server-side process to remove the first XX bit of video, but it's arbitrary; we don't know how much to trim, and we don't want to risk losing any user data.
Here's a generalization of the code we used:
private function init():void
{
var my_errors:Array = [];
if ( !Camera.isSupported )
{
my_errors.push( 'camera is not supported' );
}
else
{
camera = Camera.getCamera();
if ( !camera )
{
my_errors.push( 'no camera found' );
}
else if ( camera.muted )
{
Security.showSettings( SecurityPanel.PRIVACY );
}
}
mic = Microphone.getMicrophone();
if ( !mic )
{
my_errors.push( 'no microphone found' );
}
if ( my_errors.length )
{
this.fatal_error( my_errors );
return;
}
camera.setMode( camera_width, camera_height, camera_fps, true );
camera.setQuality( 0, camera_quality );
netconnect = new NetConnection();
netconnect.addEventListener( NetStatusEvent.NET_STATUS, net_status_handler );
netconnect.connect( publish_url );
}
private function net_status_handler( ev:NetStatusEvent ):void
{
switch ( ev.info.code )
{
case 'NetConnection.Connect.Success':
trace( 'CONNECT: Connected to "' + publish_url + '"' );
begin_stream();
break;
}
}
private function begin_stream():void
{
if ( this.recording )
return;
this.recording = true;
guid = GUID.create();
netstream = new NetStream( netconnect );
netstream.addEventListener( NetStatusEvent.NET_STATUS, net_stream_handler );
netstream.attachCamera( camera );
netstream.attachAudio( mic );
netstream.publish( guid, 'record' );
}