Recording 🔗



OpenVidu Server can be configured to record sessions. Two modes of recordings are available:

  • COMPOSED: every publisher stream is composed in the same video file in a grid layout. You can use the default layout, that will evenly distribute each stream in the available space, or you can use your own custom layout implemented with HTML/CSS/JS.

  • INDIVIDUAL: every publisher stream is recorded in its own file, generating a ZIP file containing all videos along with a text file with synchronization data. This recording mode cannot directly produce a single mixed video file of all the streams of the session, but is much more efficient than COMPOSED mode (which is quite demanding in terms of CPU).



How to record sessions 🔗

1. Enable OpenVidu recording module 🔗

For OpenVidu production deployments 🔗

Configure the following property in the .env file at OpenVidu installation path (default to /opt/openvidu)

OPENVIDU_RECORDING=true

There are other environment variables related to recordings configuration that may be set.
Visit OpenVidu configuration to see the full list.

For OpenVidu development docker container 🔗

If your are using the official openvidu/openvidu-server-kms docker container in your development environment and want to enable the recording module, then run it like this:

docker run -p 4443:4443 --rm \
    -e OPENVIDU_RECORDING=true \
    -e OPENVIDU_RECORDING_PATH=/PATH/TO/VIDEO/FILES \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /PATH/TO/VIDEO/FILES:/PATH/TO/VIDEO/FILES \
openvidu/openvidu-server-kms:2.14.0

The two configuration properties that must be set are:

  • OPENVIDU_RECORDING: enables OpenVidu recording module. Another Docker image (openvidu/openvidu-recording) will be downloaded only during the first run of the container.
  • OPENVIDU_RECORDING_PATH: where to store the recorded video files on the host machine. OpenVidu Server must have write access to this path.

It is also necessary to mount 2 volumes:

  • -v /var/run/docker.sock:/var/run/docker.sock: gives OpenVidu development container access to the Docker daemon.
  • -v /PATH/TO/VIDEO/FILES:/PATH/TO/VIDEO/FILES: gives access to the recorded video files through the container.

IMPORTANT! /PATH/TO/VIDEO/FILES must be the same in property OPENVIDU_RECORDING_PATH=/PATH/TO/VIDEO/FILES and in both sides of volume flag -v /PATH/TO/VIDEO/FILES:/PATH/TO/VIDEO/FILES

There are other environment variables related to recordings configuration that may be set.
Visit OpenVidu configuration to see the full list.



2. Configure your Sessions to be recorded 🔗


Recording can be configured in two ways:

  • ALWAYS: the session will be automatically recorded from the moment the first participant starts publishing.

  • MANUAL: you will have to tell OpenVidu when to start the recording of the session.

In both cases you can stop the recording manually, and every recording will always be automatically stopped if last user leaves the session and certain timeout elapses (see Automatic stop of recordings).


You can use REST API or any of the server SDKs (openvidu-java-client, openvidu-node-client) to manage your recorded sessions.

  1. Initialize your sessions with this POST method: POST /api/sessions
    You may configure default values for recordings started for this session by sending params such as defaultOutputMode or defaultRecordingLayout. This way you can pre-configure recordings that will be automatically started (for sessions with {"recordingMode": "ALWAYS"}). For these sessions configured with ALWAYS recording mode, no more steps are needed.

  2. If you have configured your session with "recordingMode": "MANUAL"

    • Start the recording with this POST method: POST /api/recordings/start
      You can pass parameters to override default recording configuration values set in step 1 and to further configure it with other available options

    • Stop the recording with this POST method: POST /api/recordings/stop




Composed recording 🔗

Every publisher stream is composed in the same video file in a grid layout. This is the default recording mode, and it will generate as output an MP4 file.

You can use the default layout, that will evenly distribute each stream in the available space, or you can use your own custom layout.
To use the default layout:

When starting the recording of a session with method POST /api/recordings/start pass parameters
{"outputMode: "COMPOSED", "recordingLayout": "BEST_FIT"}


For example, for a session with two publishers the video file will look like this when using output mode COMPOSED and recording layout BEST_FIT


Notes on COMPOSED recordings

  • If a COMPOSED recording is configured to record video (that is, not being an audio-only recording), this type of grid recording can be a pretty heavy consuming process. A maximum number of 4 publishers is recommended, and starting more than 2 recordings of this type at the same time can overload server CPUs. For these reasons, it is desirable to launch OpenVidu Server in a host with significant CPU power if COMPOSED video recordings are expected. In comparison, INDIVIDUAL stream recording (and COMPOSED audio-only recording) can be 4x up to 10x more efficient

  • You can configure the resolution of the MP4 file for COMPOSED recordings by using resolution property when starting the recording

  • A thumbnail got from the middle of the video will be generated for COMPOSED recordings that have video. They will be stored next to the MP4 file and named [RECORDING_ID].jpg



Individual stream recording 🔗

Every publisher stream is recorded in its own file. The final result is a ZIP file containing one WEBM file for each published stream during the recording (named after each stream identifier), along with a text file with synchronization information.

When starting the recording of a session with method POST /api/recordings/start pass parameter {"outputMode:"INDIVIDUAL"}


For example, for a session with 2 publishers the final content of the ZIP file could be:

MyRecording.zip
+-- MyRecording.json
+-- tzk08sgffcqqwpor_CAMERA_DFDAE.webm
+-- ugkmpnz4bn6yewbi_CAMERA_PAHHB.webm

And the content of the JSON synchronization file might be:

{
  "createdAt": 1548947712287,
  "id": "zfgmthb8jl9uellk",
  "name": "MyRecording",
  "sessionId": "zfgmthb8jl9uellk",
  "files": [
    {
      "connectionId": "ugkmpnz4bn6yewbi",
      "streamId": "ugkmpnz4bn6yewbi_CAMERA_PAHHB",
      "size": 4006190,
      "clientData": "",
      "serverData": "UserA",
      "hasAudio": true,
      "hasVideo": true,
      "typeOfVideo": "SCREEN",
      "startTimeOffset": 95,
      "endTimeOffset": 56445
    },
    {
      "connectionId": "tzk08sgffcqqwpor",
      "streamId": "tzk08sgffcqqwpor_CAMERA_DFDAE",
      "size": 2404760,
      "clientData": "",
      "serverData": "UserB",
      "hasAudio": false,
      "hasVideo": true,
      "typeOfVideo": "CAMERA",
      "startTimeOffset": 148,
      "endTimeOffset": 56398
    }
  ]
}


These are the properties in the JSON file

  • createdAt: time when the recording was started in UTC milliseconds
  • id: unique identifier of the recording
  • name: custom name of the recording. You can set this parameter when starting the recording, and the final ZIP file will be named after it
  • sessionId: unique identifier of the session that was recorded
  • files: array containing one JSON object for each one of the WEBM videos inside the ZIP file
    • connectionId: unique identifier of the connection that published the stream
    • streamId: unique identifier of the recorded stream
    • size: size in bytes of this particular recorded file
    • clientData: data associated to the connection that published the stream, in the client side. You can use this field to identify the user that published this particular recorded stream
    • serverData: data associated to the connection that published the stream, in the server side. You can use this field to identify the user that published this particular recorded stream
    • hasAudio: whether this recorded stream has an audio track or not
    • hasVideo: whether this recorded stream has a video track or not
    • typeOfVideo: type of video ("CAMERA" or "SCREEN"). Only defined if hasVideo is true
    • startTimeOffset: the offset in milliseconds for when this particular stream started being recorded, from the createdAt property of the root element
    • endTimeOffset: the offset in milliseconds for when this particular stream stopped being recorded, from the createdAt property of the root element



Audio-only and video-only recordings 🔗

By default recordings will be generated with both audio and video, but you can configure them to record audio-only or video-only files.

When starting the recording of a session with method POST /api/recordings/start simply pass parameters hasAudio or hasVideo with the desired values.

Notes on audio/video only recordings

  • Recordings configured to not record neither audio nor video will fail to start, returning a status error of 422

  • COMPOSED video-only recordings will generate an MP4 file. COMPOSED audio-only recordings will generate a WEBM file. INDIVIDUAL recordings will always generate a ZIP file containing one WEBM file for each recorded stream

  • Streams published during a video-only recording that are audio-only won't be recorded: they won't be included in the grid layout for COMPOSED recordings and won't generate a WEBM file in INDIVIDUAL recordings. Same for audio-only recordings with video-only streams

  • Recordings started automatically (with recording mode ALWAYS) will record both audio and video



Automatic stop of recordings 🔗

Any started recording will automatically be stopped when any of the following situations occur and certain timeout elapses. This timeout is by default 120 seconds, but you can configure it with system property OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT. The automatic recording stop timout will start:

  • For any recorded session, if last user disconnects from the session.

  • For sessions with recording mode MANUAL, if the recording is started and no user is publishing a stream.

The only condition to abort the timeout is to have any user publishing a stream to the session within the timeout.

During the timeout sessions will always remain opened and the recording active. If the timeout elapses and no stream is being published to the session, the recording will be stopped. If in addition there's no user connected to the session, the session will also be immediately closed.

You can always manually stop any recording at any time, even during the automatic stop timeout:



Custom recording layouts 🔗

You can create your own layouts for the session recording process. They are implemented with HTML/CSS/JS files, just as your OpenVidu application client-side.

1. Create your layout with HTML/CSS/JS files 🔗

Put them in a path accessible to openvidu-server. There must be an index.html file as entrypoint for your custom layout:


  • WHAT SHOULD YOUR JS CODE DO: by making use of openvidu-browser.js library, you need to connect a recorder participant to the session. This means:

    1) Your layout must connect to the session using a token like this:

    'wss://' + location.host + '?sessionId=' + SESSION_ID + '&secret=' + SECRET + '&recorder=true';
    

    Being SESSION_ID and SECRET two parameters that will be url-encoded under ids sessionId and secret respectively. So, for example:

    var url = new URL(window.location.href);
    var SESSION_ID = url.searchParams.get("sessionId");
    var SECRET = url.searchParams.get("secret");
    var TOKEN = 'wss://' + location.host + '?sessionId=' + SESSION_ID + '&secret=' + SECRET + '&recorder=true';
    var session = OV.initSession();
    session.connect(TOKEN);
    

    2) You will need to subscribe to, at least, one event: streamCreated of Session object. That way you can subscribe your recorder to every stream when any user starts publishing (by default, the video element will be automatically removed on every streamDestroyed event). To sum up, this would be the simplest code you need to properly start your recorder participant:

    var OV = new OpenVidu();
    
    var url = new URL(window.location.href);
    var SESSION_ID = url.searchParams.get("sessionId");
    var SECRET = url.searchParams.get("secret");
    var TOKEN = 'wss://' + location.host + '?sessionId=' + SESSION_ID + '&secret=' + SECRET + '&recorder=true';
    var session = OV.initSession();
    
    session.on("streamCreated", (event) => {
        session.subscribe(event.stream, 'html-id-where-insert-video');
    });
    
    session.connect(TOKEN);
    


  • HOW TO IDENTIFY YOUR USERS: you can identify them by making use of property Stream.connection.data of the Stream object retrieved in Session event "streamCreated". That way you may know which particular user should be displayed in which particular HTML element of your layout. For example:
    session.on("streamCreated", (event) => {
        var stream = event.stream;
        if (stream.connection.data === 'userBigVideo') {
            session.subscribe(stream, 'big-video-div');
        } else if (stream.connection.data === 'userSmallVideo') {
            session.subscribe(stream, 'small-video-div');
        }
    });
    



2. Configure custom layouts in OpenVidu Server 🔗

You can configure where should OpenVidu Server look for your custom layout in the system.
Default path is /opt/openvidu/custom-layout, but you can configure it with system property OPENVIDU_RECORDING_CUSTOM_LAYOUT. OpenVidu Server must have read access to that path, where you must have stored the index.html file of your layout.


OpenVidu production deployments

Default path /opt/openvidu/custom-layout is the recommended one. But if for any reason you want to change it, then set the following property in the .env configuration file:

OPENVIDU_RECORDING_CUSTOM_LAYOUT=/PATH/TO/INDEX/CUSTOM/LAYOUT


OpenVidu development docker container (development environment)

docker run -p 4443:4443 --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /PATH/TO/VIDEO/FILES:/PATH/TO/VIDEO/FILES \
    -v /PATH/TO/INDEX/CUSTOM/LAYOUT:/PATH/TO/INDEX/CUSTOM/LAYOUT \
    -e OPENVIDU_RECORDING=true \
    -e OPENVIDU_RECORDING_PATH=/PATH/TO/VIDEO/FILES \
    -e OPENVIDU_RECORDING_CUSTOM_LAYOUT=/PATH/TO/INDEX/CUSTOM/LAYOUT \
openvidu/openvidu-server-kms:2.14.0

WARNING: remember to add the -v option mounting the path defined with OPENVIDU_RECORDING_CUSTOM_LAYOUT



3. Configure your recordings to use your custom layout 🔗

When starting the recording of a session with method POST /api/recordings/start pass parameters
{"outputMode": "COMPOSED", "recordingLayout": "CUSTOM"}



Configuring multiple custom layouts 🔗

You can implement as many custom recording layouts as you want. Simply store each one of them (each one with its own index.html entrypoint file) in a subfolder under path defined with system property OPENVIDU_RECORDING_CUSTOM_LAYOUT (default value /opt/openvidu/custom-layout). Then, when configuring your sessions as stated above in point 3, just add a new parameter:

When starting the recording of a session with method POST /api/recordings/start pass parameters
{"outputMode": "COMPOSED", "recordingLayout": "CUSTOM", "customLayout": "RELATIVE/PATH/TO/INDEX"}


In the snippets above, string RELATIVE/PATH/TO/INDEX is the path from openvidu-server configuration property OPENVIDU_RECORDING_CUSTOM_LAYOUT to the specific index.html you want to use for a particular recording. So, if you have the following folder tree structure in your OpenVidu Server host:

/opt
+-- /openvidu
|   +-- /my_custom_layouts
|       +-- index.html
|       +-- /layout1
|           +-- index.html
|       +-- /layout2
|           +-- index.html
/etc
    ...

You should start openvidu-server with property OPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/my_custom_layouts and you can use any of the 3 index.html files for recording any of your sessions. To use the outer layout in a recording, just configure in recording properties recordingLayout to CUSTOM. To use any of the inner layouts, also configure customLayout to layout1 or layout2.



Using an external custom layout 🔗

OpenVidu allows you to configure a recording to use a custom layout deployed outside OpenVidu host. This is useful if, for whatever reason, you have your layout being served in a different server. To achieve this, you just have to configure the complete URL where your layout is served in property customLayout:

When starting the recording of a session with method POST /api/recordings/start pass parameters
{"outputMode": "COMPOSED", "recordingLayout": "CUSTOM", "customLayout": "https://USER:[email protected]:8888/path?myParam=123"}


As you can see, this URL may have credentials and any query parameter or token you may need in your custom layout.
For example, in the snippets above the layout files would be protected by Basic Auth with "USER" ans "PASS" as username and password, and you could access value 123 in your layout JS code just by calling
new URL(window.location.href).searchParams.get("myParam");



Debugging your custom layouts 🔗

To debug your custom layout, you just need to store it in the path declared with property OPENVIDU_RECORDING_CUSTOM_LAYOUT, as explained in section Configure custom layouts in OpenVidu Server.

Then, by using your OpenVidu application, start a session and as many publishers as you expect to be recorded in your custom layout. Finally you just have to connect to your layout through Chrome by entering url:

https://OPENVIDUAPP:SECRET@OPENVIDU_IP:OPENVIDU_PORT/layouts/custom/index.html?sessionId=SESSION_ID&secret=SECRET

Being:

  • SECRET: parameter OPENVIDU_SECRET configured when launching openvidu-server
  • OPENVIDU_IP: the IP where openvidu-server is accessible in your development machine. You will be probably using openvidu-server-kms docker container in your development environment, so this parameter is localhost if you are in Mac or Linux, and the docker IP of the container if you are in Windows (see this FAQ)
  • OPENVIDU_PORT: port where openvidu-server is listening. In OpenVidu production deployments this is by default 443 and if using the development container it is by default 4443.
  • SESSION_ID: the session ID you have initialized for the debugging process. Here's a little tip: you can initialize the session in openvidu-server (REST API, openvidu-java-client, openvidu-node-client) configuring parameter customSessionId to fix this session ID and avoid having to change it every time you restart your session.

By connecting with Chrome to the above URL you will see the exact result obtained when recording a session with your custom layout. You can open the browsers console to debug any error, and you can also change the HTML/CSS/JS files of your layout until you are happy with the outcome. Refresh the browser's tab after any change in the HTML/JS/CSS files to see the changes.



Sample custom layout 🔗

This is literally the simplest HTML for a custom recording layout. Use it as a template for building more complex ones (you will need latest openvidu-browser-VERSION.min.js file to be in the same folder. Be sure to use the same version number as your openvidu-server!)

<html>

<head><script src="openvidu-browser-2.8.0.min.js"></script></head>

<body>
    <div id="videos"></div>
</body>

<script>
    var url = new URL(window.location.href);
    var SESSION_ID = url.searchParams.get("sessionId");
    var SECRET = url.searchParams.get("secret");
    var TOKEN = 'wss://' + location.host + '?sessionId=' + SESSION_ID + '&secret=' + SECRET + '&recorder=true';

    var OV = new OpenVidu();
    var session = OV.initSession();

    session.on("streamCreated", (event) => {
        session.subscribe(event.stream, 'videos');
    });
    session.connect(TOKEN)
        .then(() => { console.log('Recorder participant connected') })
        .catch(error => { console.error(error) });
</script>

</html>




Local recording in the browser 🔗

OpenVidu Browser offers an extremely simple API to record Streams directly in the client's browser. Check it out here.