Thursday, January 12, 2012

Local video/audio recording in Adobe AIR using embedded Red5 media server

Hi everyone.

Just wanna share some experience I got by investigating the possibility to develop
local recorder using Adobe AIR + Red5 media server .

You will ask,
why Adobe AIR? Because I have been working with Flex and AIR almost 4 years and i find these technologies very attractive for all - clients and developers.
Why Red5? Because it is free, easy to install and use.
Initially the task was to migrate web-based enterprise application which uses FMS remotely to standalone AIR app with own media server locally. But before, it was required to develop some proof of concept (POC) to understand all pro et contra.

Here is folders' structure of POC:
As you can see, folder server contains Red5 media server. Fortunately, Red5 doesn't require to be installed etc. You can just copy the server's folder and to run the server by red5.bat. Folder server is included in src folder and because it is not an embedded asset, it will be moved in bin-debug folder and after  app installation, Red5 will be in installation directory, exactly what we need!!!


In general, we should do next steps:

1. Start Red5 server on application initialization.

Starting from AIR2.6 (if i am not mistaken), AIR can run native processes. HERE is just perfect article what it is and how to use it.
In my code, Red5 starting up is implemented in next way (code a bit messy but for POC and for general understanding it is enough I guess):
package com.localrecordingred5
{
  import flash.desktop.NativeProcess;
  import flash.desktop.NativeProcessStartupInfo;
  import flash.events.DataEvent;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.NativeProcessExitEvent;
  import flash.events.ProgressEvent;
  import flash.events.TimerEvent;
  import flash.filesystem.File;
  import flash.utils.Timer;
  
  import mx.controls.Alert;
  import mx.core.FlexGlobals;

  [Event(name="started", type="flash.events.Event")]
  [Event(name="stopped", type="flash.events.Event")]
  [Event(name="logging", type="flash.events.DataEvent")]
  public class Red5Manager extends EventDispatcher
  {
    private static const RED5_FOLDER:String = "server";
    
    private var logsProcessor:LogsProcessor = new LogsProcessor();
        
    private var forceCloseProcessTimer:Timer = new Timer(10000);
    private var startRed5Process:NativeProcess;
    
    /**
     * Constructor
     */
    public function Red5Manager() : void
    {
      logsProcessor.addEventListener("started", dispatchEvent);
      logsProcessor.addEventListener("shuttedDown", shuttedDown);
      logsProcessor.addEventListener("addressInUse", addressInUse);
    }
    
    private function shuttedDown(e:Event) : void
    {
      if (startRed5Process) startRed5Process.exit();
    }
    
    private function addressInUse(e:Event) : void
    {
      Alert.show("IP-port is busy. Please stop the process on port 1935.", "Ooops", 4, null, okHandler);
      
      function okHandler(e:Event) : void
      {
        stop();
      }
    }
        
    public function start() : void
    {
      launchRed5();
    }
    
    public function stop() : void
    {
      if (startRed5Process)
      {
        forceCloseProcessTimer.addEventListener(TimerEvent.TIMER, forcedExit);
        forceCloseProcessTimer.start();
        
        LocalRecordingRed5(FlexGlobals.topLevelApplication).blockUI("Shutting down...");
        
        shutdownRed5();
      }
      else
      {
        onProcessExit();
      }
      
      function forcedExit(e:TimerEvent) : void
      {
        startRed5Process.exit(true);
      }
    }
        
    public function launchRed5() : void
    {
      invokeRed5(true);
    }
    
    public function shutdownRed5() : void
    {
      invokeRed5(false);
    }
    
    private function invokeRed5(launch:Boolean) : void
    {
      var startupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
      startupInfo.executable = ConfigurationManager.getInst().getJavaFile();
      startupInfo.workingDirectory = File.applicationDirectory.resolvePath(RED5_FOLDER);
      
      var separator:String = ConfigurationManager.isWin ? ";" : ":";
      
      var arg5:String = File.applicationDirectory.resolvePath('server/boot.jar').nativePath;
        arg5 +=  separator;
        arg5 +=  File.applicationDirectory.resolvePath('server/conf').nativePath;
        arg5 +=  separator + "." + separator;
        
      var processArguments:Vector.<String> = new Vector.<String>();
      processArguments[0] = "-Dpython.home=lib";
      processArguments[1] = "-Dlogback.ContextSelector=org.red5.logging.LoggingContextSelector";
      processArguments[2] = "-Dcatalina.useNaming=true";
      processArguments[3] = "-Djava.security.debug=failure";
      processArguments[4] = "-cp";
      processArguments[5] = arg5;
      processArguments[6] = launch ? "org.red5.server.Bootstrap" : "org.red5.server.Shutdown";
      if (!launch)
      {
        processArguments[7] = "9999";
        processArguments[8] = "red5user";
        processArguments[9] = "changeme";
      }
      startupInfo.arguments = processArguments;
      
      startProcess(startupInfo);
    }
    
    private function startProcess(startupInfo:NativeProcessStartupInfo) : void
    {
      startRed5Process = new NativeProcess();
      startRed5Process.addEventListener(NativeProcessExitEvent.EXIT, onProcessExit);
      startRed5Process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onError);
      startRed5Process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutput);
      startRed5Process.start(startupInfo);
    }
    
    private function onProcessExit(e:Event = null) : void
    {
      dispatchEvent(new Event("stopped"));
    }    
    
    private function onError(event:ProgressEvent) : void
    {
      var process:NativeProcess = event.target as NativeProcess;
      var v:String = process.standardError.readUTFBytes(process.standardError.bytesAvailable);
      dispatchLogEvent(v);
    }
    
    private function onOutput(event:ProgressEvent) : void
    {
      var process:NativeProcess = event.target as NativeProcess;
      var v:String = process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable);
      dispatchLogEvent(v);
    }
    
    private function dispatchLogEvent(log:String) : void
    {
      logsProcessor.process(log);
      dispatchEvent(new DataEvent("logging", false, false, log));
    }
  }
}

I will not deep into details of each line of code, but major points are here.
Server should be started somehow from AIR.
In the beginning I tried to run .bat file with native process however it is impossible. Than i tried to use cmd.exe to start run.bat but this solution is also bad, because your code will be platform and even OS versions dependent. Finally, after investigation of run.bat content, I moved it in actionscript. See function invokeRed5 which emulates invocation of run.bat (to start the Red5 if boolean parameter is 'true') and red5-shutdown.bat (to shutdown the server if parameter is 'false'). NativeProcess dispatches all the logs from server by so called ProgressEvents. And it even separates Java errors and standard Java logs by different event names! ProgressEvent.STANDARD_ERROR_DATA and  ProgressEvent.STANDARD_OUTPUT_DATA.  Nice, ha?!... 
LogsProcessor.as processes the logs and dispatches appropriate events. It is done in a bit dirty way by checking the text matching however, right now, I don't have any better solution. If you find smarter way, please let me know.
package com.localrecordingred5
{
  import flash.events.Event;
  import flash.events.EventDispatcher;

  [Event(name="started", type="flash.events.Event")]
  [Event(name="shuttedDown", type="flash.events.Event")]
  [Event(name="addressInUse", type="flash.events.Event")]
  public class LogsProcessor extends EventDispatcher
  {
    private static const LAUNCHED:String = "Installer service created";
    private static const SHUTTED_DOWN:String = "Stopping Coyote";
    private static const ADDRESS_IN_USE:String = "Address already in use";
    
    public function process(log:String) : void
    {
      if (log.indexOf(LAUNCHED) > -1)
      {
        dispatchEvent(new Event("started", true));
      }
      if (log.indexOf(SHUTTED_DOWN) > -1)
      {
        dispatchEvent(new Event("shuttedDown", true));
      }
      if (log.indexOf(ADDRESS_IN_USE) > -1)
      {
        dispatchEvent(new Event("addressInUse", true));
      }
    }
  }
}

Oh... and don't forget to set this configuration in your AIR *-app.xml:
<supportedProfiles>extendedDesktop</supportedProfiles>


1.1. Check Java path

Notice, that executable file of native process is javaw.exe for Windows and java for Mac. AIR doesn't know where is Java installed, that's why you should check it once with user and save it in some configuration file.
Here are my ConfigurationManager and ConfigPopup which do all this stuff:

 ConfigurationManager.as 
package com.localrecordingred5
{
  import flash.display.DisplayObject;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
  import flash.system.Capabilities;
  
  import mx.core.FlexGlobals;
  import mx.managers.PopUpManager;
  
  [Event(name="configured", type="flash.events.DataEvent")]
  public class ConfigurationManager extends EventDispatcher
  {
    private static const CONFIG_FILE:String = "config.xml";
      
    private static var instance:ConfigurationManager;
    
    private var configFile:File;
    private var _config:ConfigVO;
    private var configPopup:ConfigPopup;
    
    /**
     * Configuration
     * 
     */
    public function ConfigurationManager(enforcer:SingletonEnforcer)
    {
      if (!enforcer) throw new Error("ConfigurationManager can't be explicitly instantiated");
      
      configFile = File.applicationStorageDirectory.resolvePath(CONFIG_FILE);
    }
    
    public static function getInst() : ConfigurationManager
    {
      if (!instance)
        instance = new ConfigurationManager(new SingletonEnforcer());
      return instance;
    }
    
    //-----------------------------------------------------
    //
    //  PUBLIC methods
    //
    //-----------------------------------------------------
    public function get config() : ConfigVO
    {
      return _config;
    }
    
    /**
     * Check configuration.
     * If everything is configured, dispatch the event and continue app loading.
     * Otherwise, show config popup.
     */
    public function check() : void
    {
      var cfgXml:XML = readConfig();
      populateConfig(cfgXml);
      if (isJavaHomeValid())
      {
        dispatchEvent(new Event("configured"));
      }
      else
      {
        configPopup = PopUpManager.createPopUp(FlexGlobals.topLevelApplication as DisplayObject, ConfigPopup, true) as ConfigPopup;
        configPopup.addEventListener("saveConfig", save);
        PopUpManager.centerPopUp(configPopup);
      }
    }
        
    public function getJavaFile() : File
    {
      // Get a file reference to the JVM
      var file:File = new File(_config.javaHome);
      if (isWin)
      {
        file = file.resolvePath("bin/javaw.exe");
      }
      else
      {
        file = file.resolvePath("Home/bin/java");
      }
      return file;
    }
           
    private function save(e:Event) : void
    {
      var xml:XML = 
        <config>
          <javaHome>{config.javaHome}</javaHome>
        </config>;
      var fs:FileStream = new FileStream();
      fs.open(configFile, FileMode.WRITE);
      fs.writeUTFBytes(xml);
      fs.close();
      
      PopUpManager.removePopUp(configPopup);
      dispatchEvent(new Event("configured"));
    }
    
    private function populateConfig(configXml:XML) : void
    {
      _config = new ConfigVO();
      if (configXml)
      {
        _config.javaHome = new String(configXml..javaHome);
      }
    }
    
    public function isJavaHomeValid() : Boolean
    {
      if (!_config) return false;
      
      // If no known last home, present default/sample values
      if (!_config.javaHome) setDefaultJavaHome();
      
      return getJavaFile().exists;
      
      function setDefaultJavaHome() : void
      {
        _config.javaHome = (isWin) ? 
          "C:\\Program Files\\Java\\jre6" : 
          "/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0";
      }
    }
        
    private function readConfig():XML
    {
      if (configFile.exists)
      {
        var fs:FileStream = new FileStream();
        fs.open(configFile, FileMode.READ);
        var xml:XML = XML(fs.readUTFBytes(fs.bytesAvailable));
        fs.close();
        return xml;
      }
      else
      {
        return null;
      }
    }
    
    public static function get isWin() : Boolean
    {
      return Capabilities.os.toLowerCase().indexOf("win") > -1;
    }
  }
}
class SingletonEnforcer{}

ConfigPopup.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009" 
     xmlns:s="library://ns.adobe.com/flex/spark" 
     xmlns:mx="library://ns.adobe.com/flex/mx" 
     title="Configuration"
     creationComplete="init()">
  
  <fx:Script>
    <![CDATA[
      import mx.events.FlexEvent;
      import mx.utils.StringUtil;
      
      private var cfgMngr:ConfigurationManager;
      
      private function init():void
      {
        cfgMngr = ConfigurationManager.getInst();
        javaTI.text = cfgMngr.config.javaHome;
      }
      
      private function onSave():void
      {
        cfgMngr.config.javaHome = StringUtil.trim(javaTI.text);
        if (cfgMngr.isJavaHomeValid())
        {
          dispatchEvent(new Event("saveConfig"));
        }
        else
        {
          errLbl.text = "Java Home is not configured properly. \nPlease check again";
        }
      }
      
      
      
    ]]>
  </fx:Script>
  
  <s:layout>
    <s:VerticalLayout paddingBottom="0" paddingLeft="0" paddingRight="0" paddingTop="0"/>
  </s:layout>

  <s:Form width="100%" >
  
    <s:layout>
      <s:FormLayout gap="0" />
    </s:layout>
    
    <s:FormItem width="100%">
      <s:Label maxDisplayedLines="3" width="100%" color="#6c8dae"
           text="Please configure the application once and enjoy your local video recording."/>  
    </s:FormItem>
    <s:FormItem label="Java Home:" width="100%">
      <s:TextInput id="javaTI" width="100%"/>
    </s:FormItem>
    <s:FormItem width="100%">
      <s:layout>
        <s:VerticalLayout paddingTop="0"/>
      </s:layout>
      <mx:HRule width="100%"/>
      <s:HGroup width="100%" height="30">
        <s:Label id="errLbl" color="red" width="100%"/>
        <s:Button label="Save" click="onSave()"/>
      </s:HGroup>
    </s:FormItem>
  </s:Form>  
  
</s:Panel>


ConfigVO.as
package com.localrecordingred5
{
  [Bindable]
  public class ConfigVO
  {
    public var javaHome:String;
  }
}


2. Establish connection with Red5 from Flash.

Now, when server is up and running as a Java process, it is the time to establish NetConnection and to create NetStream.
URL connection is always rtmp://localhost:{configured port}/{name of app}. In my case, it is rtmp://localhost:1935/mytest/.

public function connect():void
{
  connection = new NetConnection();
  connection.addEventListener(NetStatusEvent.NET_STATUS, netConnectionStatusHandler);
  connection.connect(RTMP_SERVER);
}

private function netConnectionStatusHandler(event:NetStatusEvent) : void
{
  var evCode:String = event.info.code;
  
  switch (evCode)
  {
    case "NetConnection.Connect.Success":
      currentState = STOP_STATE;
      createStream();
      dispatchEvent(new Event("connected"));
      break;
    ...
  }
}
      
private function get cam() : Camera
{
  var camera:Camera = Camera.getCamera();
  camera.setQuality(0, qualityNS.value);
  var res:Object = resCB.selectedItem;
  camera.setMode(res.width, res.height, frameNS.value);
  return camera;
}

private function get mic() : Microphone
{
  return Microphone.getMicrophone();
}

private function createStream() : void
{
  var cam:Camera = this.cam;
  vd.attachCamera(cam);
  stream = new NetStream(connection);
  stream.addEventListener(NetStatusEvent.NET_STATUS, netConnectionStatusHandler);
  stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
  stream.attachCamera(cam);
  stream.attachAudio(mic);
  stream.bufferTime = BUFFER_TIME;
  createStreamName();
}

//create some random name for test
private function createStreamName() : void
{
  streamName = "video" + Math.round(Math.random() * 100000);
  dispatchLogEvent("Stream name generated: " + streamName + "\n");
}

As you can see from snippet above, on NetConnection.Connect.Success the stream is created.

Recording flow is primitive.
On record button click, the stream starts is published:
stream.publish(streamName, "record");

On record stop, the flag is set that stop is requested + camera and mic are unattached:
private function onStop() : void
{
  stream.attachCamera(null);
  stream.attachAudio(null);
  stopRequested = true;
  LocalRecordingRed5(FlexGlobals.topLevelApplication).blockUI("Processing...");
}

Application is waiting until stream's buffer is empty. Whet it is, stream should be closed:
case "NetStream.Buffer.Empty":
  if (stopRequested)
  {
    stop();
    LocalRecordingRed5(FlexGlobals.topLevelApplication).unblockUI();
    stopRequested = false;
    recording = false;
  }
  break;
  
  ...

private function stop():void 
{
  ...;
  stream.close();
}

Now, the last event in my flow - stream is unpublished successfully and I can enjoy my video.

case "NetStream.Unpublish.Success":
  var f:File = File.applicationDirectory.resolvePath("server/webapps/mytest/streams/" + streamName + ".flv");
  f.addEventListener(Event.SELECT, saveByPath);
  f.browseForSave(streamName + ".flv");
  break;

Notice, that Red5 writes the FLVs into directory 'streams' under your server application directory. I don't know how to configure this path but looks like this
investigation will be required in the nearest feature :)


3. Server's application 'mytest'

Regarding to directory 'mytest'... this is your, so called, web application. In few words, Red5 is based on Apache Tomcat web-server and 'mytest' can contain all your html assets, JSPs, compiled Java classes etc. In my case, 'mytest' contains configured by default web.xml (nothing special inside),
red5-web.properties
webapp.contextPath=/mytest
webapp.virtualHosts=localhost, 127.0.0.1

and red5-web.xml which is based on Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                           http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
  
  <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="location" value="/WEB-INF/red5-web.properties" />
  </bean>
  
  <bean id="web.context" class="org.red5.server.Context" autowire="byType" />
  
  <bean id="web.scope" class="org.red5.server.WebScope"
     init-method="register">
    <property name="server" ref="red5.server" />
    <property name="parent" ref="global.scope" />
    <property name="context" ref="web.context" />
    <property name="handler" ref="web.handler" />
    <property name="contextPath" value="${webapp.contextPath}" />
    <property name="virtualHosts" value="${webapp.virtualHosts}" />
  </bean>
  
  <bean id="web.handler" class="com.test.MyTest" /> 
</beans>

Marked in red is integration of Java class.

This class should be extended from class org.red5.server.adapter.ApplicationAdapter.
All the public methods of this class can be invoked from AIR through the connection.
E.g. here is an invocation of public method pingServer of Java class com.test.MyTest from Flash:
protected function pingServer():void
{
  connection.call("pingServer", new Responder(res, fault));
  
  function res(e:Object) : void
  {
    dispatchLogEvent("Ping is successfull. Returned value:" + e.toString() + "\n");
  }
  
  function fault(e:Object) : void
  {
    dispatchLogEvent("Ping is failed.\n");
  }
}
This is fantastic feature cause you can move some heavy calculations, data processing etc. from AIR to Java. Incredible!


4. Never forget to shut down the server when application is closing.

...because on next application starting, you will get "port is busy" error.

To do this, the easiest way is to suppress the closing event and to put the logic which will stop the Red5-Java process and, only after that, will close the application.
closing="onClosing(event)"
...
...             
protected function onClosing(event:Event):void
{
  event.preventDefault();
  red5Starter.stop();
}
...
...
private function red5Stopped(event:Event):void
{
  NativeApplication.nativeApplication.exit();
}
...
...
<fx:Declarations>
  <localrecordingred5:Red5Manager id="red5Starter" 
                  logging="red5Logging(event)"
                  started="red5Started(event)"
                  stopped="red5Stopped(event)"/>
</fx:Declarations>


Summary
That's probably it what I wanted to share with wide Flex/AIR auditory.

Of course, code is not optimized and can contain some memory leaks etc. but for POC it is not necessary.

The source is here

In folder setup, you will find already compiled AIR installion file.

Separate thanks to Christophe Coenraets for his great article Tomcat Launcher: Sample Application using the AIR 2.0 Native Process API which helped me a lot to launch Red5 under AIR.

Cheers...

70 comments:

  1. Thanks for a great post!
    Did you try it on a Mac?

    ReplyDelete
    Replies
    1. Thanks for feedback.
      Yeah. I tried it on Mac and it worked well. However I didn't have a chance to try it on Linux. So if anybody try it on Linux, please comment here about the results.

      Delete
    2. Thanks for this tutorial!!... by the way, it is posible to run bat files from Air.
      you say "I tried to run .bat file with native process however it is impossible." well it is, from version flash CS 5.5, if you know a little spanish i invite you to read my tutorial from this issue in http://www.cristalab.com/tutoriales/ejecutar-una-bat-file-con-air-2.0-c96486l/. thanks again!!

      Delete
  2. Hi Buddy

    Thanks for this Tutorial

    ReplyDelete
  3. When I run from Flash Builder I get the below, some issue with using classpath for red5.properties? Please help. Thanks

    org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [red5.properties] cannot be opened because it does not exist
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:78)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:553)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:527)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:362)
    at org.red5.server.Launcher.launch(Launcher.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.red5.server.Bootstrap.bootStrap(Bootstrap.java:106)
    at org.red5.server.Bootstrap.main(Bootstrap.java:50)
    Caused by: java.io.FileNotFoundException: class path resource [red5.properties] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:143)
    at org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties(PropertiesLoaderSupport.java:182)
    at org.springframework.core.io.support.PropertiesLoaderSupport.mergeProperties(PropertiesLoaderSupport.java:161)
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:69)

    ReplyDelete
    Replies
    1. The problem is that Flash Builder (by some strange reason) doesn't copy *.properties files as assets from src to bin-debug|bin-release.
      These .properties files are placed in folder 'src\server'. Just re-copy manually folder 'server' from 'src' to 'bin-debug|bin-release' to be sure that all the skipped files by Flash Builder are copied into output folder.

      Delete
  4. Excellent! Thank you so much, works great now.

    I notice that sometimes I get the "IP-port is busy. Please stop the process on port 1935." error.

    I believe locating and killing the process listening to 1935 will do the trick, but I cant seem to do that.... restarting fixes though.

    Did you encounter this issues and find a workaround?

    ReplyDelete
    Replies
    1. No problem! Glad that it works for you and you can the benefit from this article.
      Re. your question: looks like you don't close the application properly (usually it happens when you kill the app through Ctrl+Alt+Delete or somehow else). That's what point #4 ("Never forget to shut down the server when application is closing.") of this article is responsible for. But anyway, if you see this message, just remove the instance of javaw.exe in Task Manager (that's Red5 Java instance).

      Delete
  5. Thanks Vadym, Great work and results! I'll be back later. Grant or AdobeMaster.

    ReplyDelete
  6. Hi

    I am testing this script with Adobe Flex 4.6 and with latest SDK
    but it gives me this error
    IP-port is busy. Please stop the process on port 1935.

    I closed all of javaw.exe processes but still this error


    and on flash builder application didn't open and gives me this
    [SWF] LocalRecordingRed5.swf - 4,049,403 bytes after decompression
    [Unload SWF] LocalRecordingRed5.swf


    I tested JRE version 6 x86 and x64 , verion 7 x64
    and JDK version 6 x64
    but no result

    please can you help me to find a way to run your sample?

    Thanks

    ReplyDelete
    Replies
    1. Hi AGB.
      Do you have FMS installed locally on your PC? It uses 1935 as well.
      If you are under Windows, go to system console (Start/Run/cmd) and type 'netstat /ano'. It will show you all the process IDs (PID) and its used ports (e.g. http://i46.tinypic.com/2zqy5a0.png ).
      Hope it helps.

      Delete
  7. great work!!

    instead of Red5 is it possible to do with FFmpeg? if so where would we need to change this?

    also after using a few times its just keeps crashing on windows..

    ReplyDelete
    Replies
    1. actually, if i understand correctly, ffmpeg is used more for convertion of videos and it can't record netstream. to record the video from flash, you should connect your flash app to some media server and ffmpeg is not a media server (correct me if i am wrong). but you can integrate ffmpeg in this application to do some post-record convertion, processing etc.
      Re. your issue with crashing: can you provide some logs and screenshots?

      Delete
  8. thanks for your quick response!!

    yes i want to use ffmpeg to convert to h.264 on the fly and stream to my ipad with a segmenter....

    i have a great little batch file i use now that does this for me now.

    it just crashed the 1st time i tried using it now after a pc restart, where do i find the log file to show you? i did get a screenshot and it seems to be a ffdshow.ax

    how do i show you the screenshot? dont see a upload here

    ReplyDelete
    Replies
    1. you can use any file sharing system to upload the screenshot, the same for logs, e.g. http://www.speedyshare.com/.

      to get logs, just click on checkbox "Show Logs" and copy them OR by path "your-app/server/log/". just put logs and screenshot in some zip and upload it.

      Delete
  9. Hi Vadym,

    Thank you for this awsome stuff :-)
    I try to make your installer.exe work before diving into the code... Everything works well, but no luck : it does not record any video, in the recorder logs it keeps saying

    netConnectionStatusHandler::event.info.code = NetStream.Buffer.Empty

    Would you have any idea why the recording does not work ?

    Thanks for your feedback !

    JB

    ReplyDelete
    Replies
    1. Hi JB.

      I noticed such things under Win7 if program is installed in the folder where are no write permissions for the account (e.g. Program Files). Try to re-install it to some another permissions free folder (e.g. D:\MyTestApp\...).

      Or do you have some other issue? Also check folder \server\webapps\mytest\streams\. This is a place where FLVs are stored by Red5.

      Thanks.

      Delete
    2. Hey, that's quite a fast reply :-) Thanks dude !!!
      Good catch : indeed Win7 and its Program Files folder was blocking things !

      -> It records now well... but without the sound. In fact it looks like there's an audio track but kinda corrupted or something like this : VLC tells that it "doesn't support 'undf' audio or video format"...
      Otherwise it reads well (man can guess there's some audio) in the flv you let in the streams folder... My generated flvs are obviously not as good as your ones... and I have not much idea where this could come from... ==> maybe is that necessary to install some audio codecs ?

      If you have any idea about it... it will be welcome :-)

      JB

      Delete
    3. I may have written something wrong : it looks like there's no audio at all... I tried to open the generated flv files into Adobe Media Encoder as it usually gives quite handy info about video files... and it says there's no audio track in the flv.

      Delete
    4. Hey JB!

      I`ve just downloaded this POC from the links above (to be sure that we both use the same app installer) and re-installed it in my PC. Recorded video with audio and it works just perfectly. Audio and video are both synchronized and played. I guess you have some local issues with codecs or something like that.
      Also you are the first one who has the issue with audio. From my side, I would recommend you to reinstall your codecs. Try K-Lite http://www.codecguide.com/download_kl.htm

      Hope it helps.

      Vadym.

      Delete
    5. You're right : it was a local issue... The K-Lite pack solved this recording issue !
      If this can help anyone else...

      Thank you for sharing your nice work Vadym !

      JB

      Delete
  10. do i need to make any special adjustments to run this on a Mac? i try to run/debug it and it gets to the initial "Starting..." screen and then it just closes.

    ReplyDelete
    Replies
    1. Same problem here.
      My Java home is properly set but I'm stuck on "Starting...".
      Nothing show up on logs.

      Delete
    2. Jack and Fernando,
      in your case, I would import the source to your FlashBuilder to debug the code and to see where your code has a stuck. I guess that NativeProcess is just not started but some reason (see method 'invokeRed5').

      Delete
    3. Sorry. FDT Bug didn't allowed me to launch as extendedDesktop :(

      Delete
  11. Could you please give me an example to do a post video conversion with ffmpeg? I would like to convert video from one format to different format - eg: .avi to .flv

    Thanks in advance :)

    ReplyDelete
  12. I'm unable to create the native exe for windows. When i used adt in command prompt it says "not valid internal or external command". So i moved everything to "C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin\". Now i have "C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin\MyCam" (Mycam - project folder). And i open command prompt and the navigated to "C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin\"

    i run the following command but not nothing happens with exe and got the following errors.

    C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin>adt -package -stor
    etype pkcs12 -keystore MyCam\mycam.p12 -target native MyCam\MyCam.exe MyCam\bin-
    debug\MyCam-app.xml MyCam\bin-debug\MyCam.swf
    password:
    C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin\MyCam\bin-debug\My
    Cam-app.xml: error 302: Root content MyCam.swf is missing from package

    C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin>

    ReplyDelete
    Replies
    1. I guess console compilation of AIR using adt is out of this post scope.

      Delete
  13. This comment has been removed by a blog administrator.

    ReplyDelete
  14. I want to make a desktop sharing application using red5 server in java.could you please suggest me that what will b the structure of this project.and what r the environment which needed to deploy this application.m trying to make it since one moth but i did not get any clue about it.please suggest me.

    ReplyDelete
    Replies
    1. I am not sure I can help you here. It is quite complicated topic. Try search something in web.
      E.g. here is something what can answer on your question http://stackoverflow.com/questions/2059309/java-applet-screen-capture-to-a-video

      Delete
  15. How can i change the path that videos stored by red5. Other than this "server/webapps/mytest/streams/" to "user/videos"..?

    ReplyDelete
    Replies
    1. Red5 is responsible for stream saving. You can done it easily configuring your spring application context config file and adding one java class.
      More is here http://www.red5tutorials.net/index.php/Tutorials:Streaming_from_custom_directories

      Delete
    2. this page no longer is there are there alternatives to look at ?

      also is there a time limit to record? what if i wanted to record a 2 hour event

      and if we wanted to connect to the stream what would address be?

      Delete
  16. This is great stuff!
    and your attention to each commenter is super

    thanx!

    ReplyDelete
  17. ur welcome , and now I have a problem of my own

    I am coding on a Mac, with Flash Builder 4.6 and the 4.6 sdk.

    running or debugging my app works fine but when i export a release build (within a native installer) the (successfully) installed app exits after a few seconds of running (meaning that the red5 server has stopped and therefore the app itself quits).

    I have read all the comments and therefore I have manually copied the "server" folder to the bin-debug folder.

    I cannot really debug code since it works fine within the Flash Builder just not on the released build.

    any ideas?

    ReplyDelete
    Replies
    1. "installed app exits after a few seconds of running" - is it because 'properties' files are missed in 'server' folder after release build installation? In other words, do you have a problem only with release build version because of the missed properties?

      Delete
    2. yes Vadym thank you!

      apparently even copying the "server" folder inside Flash Builder isn't enough when coding on a Mac for multiple platforms. I did copy the "server" folder you provided and manually inserted it to the application installer for each platform

      all works fine now
      thanks

      Delete
  18. this works great!! i found that the video was pretty bad quality however, or if i raised the quality, then i would get terrible audio/video sync... but adding those lines solved it;
    Recorder.mxml:createStream(), at the end of the function add:

    var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings();
    h264Settings.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_2);
    stream.videoStreamSettings = h264Settings;

    now i can record in 1280x720 90 of quality, 30fps and its pretty decent!!

    thanks a lot for getting me started in the right direction!

    ReplyDelete
    Replies
    1. what file did you do that in? i cant find it in the folders anywhere

      Delete
  19. Hi, Vadym:

    Thank you so much for sharing your hard-won knowledge.

    I was able to successfully install your AIR application, and it works great. I also tried to use your source files in Adobe Flash Builder. Unfortunately, I have always used Flash Professional and have never used Adobe Flash Builder before. I get the following errors:

    When I try to "Export Release Build," I get the error 'Unknown Flex SDK: "Flex 4.5.1"'. Also, when I try to use the design view for the file "Recorder.mxml" I get an error message saying "An unknown item is declared as the root of your MXML document. Switch to source mode to correct it." These errors happen in both a version 4 of Flash Builder, which I own, and a trial version of version 4.6.

    I would sincerely appreciate any ideas you may have. Thank you.

    Don

    ReplyDelete
    Replies
    1. Hi Don,

      I never worked with Flash Professional, so I can't help you here. According to your errors, you don't have installed Flex SDK 4.5.1 in Flash Professional. Try to research how to setup it.

      Thank you,
      Vadym.

      Delete
  20. Thank you for your quick reply, Vadym. I'll follow up on your suggestion.

    Did you develop your video recording application in Eclipse?

    Don

    ReplyDelete
    Replies
    1. Nope, developed in Flash Builder.
      Actually, Flash Builder is extension from Eclipse, so if you are familiar with the second, it will be easy for you to work in Flash Builder. You can download latest trial 30-days release version from https://www.adobe.com/cfusion/tdrc/index.cfm?product=flash_builder

      Vadym.

      Delete
  21. Hi Vadyam,

    I have a Video Conference App which uses Adobe AIR and Red 5 as streaming server.I used to cross compile the same code base for Android and IOS...Now for IOS, due to unavailability of AEC feature for mobile devices, the voice/audio in my mobile app seems to be very choppy and cluttered...and there is a heavy lag in voice(Video-voice sync is not there also). I tried both speex and Nellymoser codecs...Nellymoser gives a decent audio feed but still will never match the requirements of a video chat over a mobile..I am badly stuck with this....Cold you please assist me on the optimal microphone settings to avoid these issues...

    -Veeru

    ReplyDelete
  22. Thanks for sharing this useful info. Keep updating same way.
    Regards,Siddu online Training

    ReplyDelete
  23. Hello friend, the file is deleted at 4shared. It has a mirror?

    ReplyDelete
    Replies
    1. Yeah, looks like 4shared dropped it.
      Uploaded to my dropbox.
      Try it now!

      Delete
    2. Thank you! I'm testing it works fine! Because the H264 does not add much difference in the size of the video?

      Delete
  24. Hi Vadym,

    Thanks for the great info & tutorial. I sent you an email recently about this technique and was wondering if you could gut check my log for major errors/things I misconfigured. I stripped the essential parts from your code so I do not have to rely on Flex/MXML, but am thinking I am missing something obvious.

    My server is properly embedded and initialized, but NetStream objects close immediately and are flaky. No file is output/recorded.

    I'd greatly appreciate any advice/tips you could provide!


    Thanks,
    — Ryan

    ReplyDelete
  25. This comment has been removed by the author.

    ReplyDelete
  26. Hi,

    I have managed to set the project up in Flash Builder 4.7.
    When launching or debugging the app in FB, the application starts but stops straight away. I don't have any error showing.
    I have tried to insert breakpoint at different place to follow the application process but once again nothing wrong there.

    Any suggestion or help would be really appreciated.

    Many thanks,

    J

    ReplyDelete
    Replies
    1. I can just recommend to check java version to be 1.6 and make sure that application has write permissions. Also flash builder has an issue - it doesnt copy properties files to bin-debug, so probably you will have to copy it manually (see comments thread for the details).
      Thanks,
      Vadym

      Delete
  27. Sorry for late response, maybe it is not actual. So what you are saying is not possible because this recorder is standalone and if you need to have it in web, you need remote media server, another flash client... long story...

    ReplyDelete
  28. Thanks for the great tool. I wonder if one could get it working on Android - I assume you would have to make a Native Extension?

    ReplyDelete
  29. Hi vadym Klos, great work :D , but the source files are no longer available in the dropbox :( could you please upload it again ? or send it to my email: dx1290@hotmail.com
    thanks in advance !!

    ReplyDelete
    Replies
    1. Damn... accidentally removed. Try again. Should be available now. Thank you.

      Delete
    2. Thank you very much Vadym :D

      Delete
  30. Thanks for the awsome post. I have a question but don't know where to ask you, so i hope you will answer here:

    I am working on a project which requires me to record Audio from Mic and Soundcard (any audio input from computer). I was successful in recording audio from Microphone and save it as mp3 file using mp3 encoder from as3-lame-air library. My question is: Is there any possibility that i can record audio from any input source instead of Microphone?

    Thanks in advance.

    ReplyDelete
  31. what is the actual ip address of the rtmp stream is say we want to view it on my LAN via VLC to watch it as well or even re broadcast it?

    ReplyDelete
  32. No longer works with latest version of Adobe AIR SDK (3.7) and Flash Builder 4.7

    ReplyDelete
  33. Hey Vadym Klos, Thanks for the great article. I am able to run it with newer AIR SDKs.

    Can you make it work with newer RED5 server?

    ReplyDelete
  34. Wonderful information your website gives the best and the most interesting information.................................Please contact us for Oracle Fusion HCM Online Training Institute

    ReplyDelete
  35. It is amazing to visit your site. Thanks for sharing this information, this is useful to me..
    Workday Online Course
    Workday Training Online

    ReplyDelete
  36. Las vegas recording studios We are really grateful for your blog post. You will find a lot of approaches after visiting your post. I was exactly searching for. Thanks for such post and please keep it up. Great work.

    ReplyDelete