Fork me on GitHub
Loading...
Searching...
No Matches
Record&Play plugin documentation

This is a simple application that implements two different features: it allows you to record a message you send with WebRTC in the format defined in recorded.c (MJR recording) and subsequently replay this recording (or other previously recorded) through WebRTC as well. For more information on how Janus implements recordings natively and the MJR format, refer to the Recordings documentation.

This application aims at showing how easy recording frames sent by a peer is, and how this recording can be re-used directly, without necessarily involving a post-processing process (e.g., through the tool we provide in janus-pp-rec.c). Notice that only audio and video can be recorded and replayed in this plugin: if you're interested in recording data channel messages (which Janus and the .mjr format do support), you should use a different plugin instead.

The configuration process is relatively straightforward: in the simplest configuration, you just choose where the recordings should be saved. The same folder will also be used to list the available recordings that can be replayed.

Notice that, by default, all recordings are public, which means any user connecting to the plugin and asking for a list of recordings will be able to obtain their IDs. Recordings can be marked as private to avoid that, meaning that users will only be able to consume such a recording if they're aware of its ID via out of band mechanisms. Marking a recording as private can be done in two different ways:

  1. via the API, that is when the recording is created;
  2. by setting the private property to true in the plugin configuration file, which will automatically mark all new recordings as private by default unless otherwise specified in the API request.
Note
The application creates a special file in INI format with .nfo extension for each recording that is saved. This is necessary to map a specific audio .mjr file to a different video .mjr one, as they always get saved in different files. If you want to replay recordings you took in a different application (e.g., the streaming or videoroom plugins) just copy the related files in the folder you configured this plugin to use and create a .nfo file in the same folder to create a mapping, e.g.:
         [12345678]
         name = My videoroom recording
         date = 2014-10-14 17:11:26
         private = false
         audio = videoroom-audio.mjr
         video = videoroom-video.mjr

Data channel recordings are supported via a data attribute as well.

Record&Play API

The Record&Play API supports several requests, some of which are synchronous and some asynchronous. There are some situations, though, (invalid JSON, invalid request) which will always result in a synchronous error response even for asynchronous requests.

list , update and configure are synchronous requests, which means you'll get a response directly within the context of the transaction. list lists all the available recordings, while update forces the plugin to scan the folder of recordings again in case some were added manually and not indexed in the meanwhile. The configure request can be used to tweak some settings while recording a session.

The record , play , start , pause , resume and stop requests instead are all asynchronous, which means you'll get a notification about their success or failure in an event. record asks the plugin to start recording a session; play asks the plugin to prepare the playout of one of the previously recorded sessions; start starts the actual playout, and stop stops whatever the session was for, i.e., recording or replaying. Recording sessions can also be dynamically paused and resumed using pause and resume : the pauses will be omitted from the recording, meaning recordings will not have holes in them, and will move from one section to the other with no pause.

The list request has to be formatted as follows:

{
        "request" : "list",
        "admin_key" : "<plugin administrator key; optional>"
}

A successful request will result in an array of recordings:

{
        "recordplay" : "list",
        "list": [       // Array of recording objects
                {                       // Recording #1
                        "id": <numeric ID>,
                        "name": "<Name of the recording>",
                        "date": "<Date of the recording>",
                        "audio": "<Audio rec file, if any; optional>",
                        "video": "<Video rec file, if any; optional>",
                        "data": "<Data rec file, if any; optional>",
                        "audio_codec": "<Audio codec, if any; optional>",
                        "video_codec": "<Video codec, if any; optional>"
                },
                <other recordings>
        ]
}

An error instead (and the same applies to all other requests, so this won't be repeated) would provide both an error code and a more verbose description of the cause of the issue:

{
        "recordplay" : "event",
        "error_code" : <numeric ID, check Macros below>,
        "error" : "<error description as a string>"
}

Notice that, as explained previously, the list request will only return the list of all recordings who were not marked as private. To return the list of private recordings as well, the right admin_key must be provided as well. In case no admin_key was configured, then the list of private recordings will never be returned.

The update request instead has to be formatted as follows:

{
        "request" : "update",
        "admin_key" : "<plugin administrator key; mandatory, if configured>"
}

which will always result in an immediate ack ( ok ):

{
        "recordplay" : "ok",
}

Notice that, if an admin_key was configured in the configuration file, it is a mandatory property to pass in an update request as well.

Coming to the asynchronous requests, record has to be attached to a JSEP offer (failure to do so will result in an error) and has to be formatted as follows:

{
        "request" : "record",
        "id" : <unique numeric ID for the recording; optional, will be chosen by the server if missing>
        "name" : "<Pretty name for the recording>",
        "is_private" : <true|false, whether the recording should be listable; the default is what was configured in the plugin config file>,
        "filename" : "<Base path/name for the file (media type and extension added by the plugin); optional>",
        "audiocodec" : "<name of the audio codec we prefer for the recording; optional>",
        "videocodec" : "<name of the video codec we prefer for the recording; optional>",
        "videoprofile" : "<in case the video codec supports, profile to use (e.g., "2" for VP9, or "42e01f" for H.264); optional>",
        "opusred" : <true|false, whether RED should be negotiated for audio, if offered; optional (default=false)>,
        "textdata" : "<in case data channels have to be recorded, whether the data will be text (default) or binary; optional>"
}

A successful management of this request will result in a recording event which will include the unique ID of the recording and a JSEP answer to complete the setup of the associated PeerConnection to record:

{
        "recordplay" : "event",
        "result": {
                "status" : "recording",
                "id" : <unique numeric ID>,
                "is_private" : <true|false, same as the request>
        }
}

Some properties of the recording session can be tweaked dynamically via the configure request, and has to be formatted as follows:

{
        "request" : "configure",
        "video-bitrate-max", <bitrate cap that should be forced (via REMB) on the recorder>,
        "video-keyframe-interval", <how often, in seconds, the plugin should send a PLI to the recorder to request a keyframe>
}

A successful request will result in a confirmation of :

{
        "recordplay" : "configure",
        "status" : "ok",
        "settings" : {
                "video-bitrate-max" : <current value of the property>,
                "video-keyframe-interval" : <current value of the property>
        }
}

You can pause the recording process (meaning that RTP packets will keep on flowing but will not be recorded to file) using the pause request:

{
        "request" : "pause",
}

This will result in a paused status:

{
        "recordplay" : "event",
        "result": {
                "status" : "paused",
                "id" : <unique numeric ID of the paused recording>
        }
}

To resume the recording process you can use the resume request:

{
        "request" : "resume",
}

This will result in a resumed status:

{
        "recordplay" : "event",
        "result": {
                "status" : "resumed",
                "id" : <unique numeric ID of the paused recording>
        }
}

A stop request can interrupt the recording process and tear the associated PeerConnection down:

{
        "request" : "stop",
}

This will result in a stopped status:

{
        "recordplay" : "event",
        "result": {
                "status" : "stopped",
                "id" : <unique numeric ID of the interrupted recording>,
                "is_private" : <whether the interrupted recording is private>
        }
}

For what concerns the playout, instead, the process is slightly different: you first choose a recording to replay, using play , and then start its playout using a start request. Just as before, a stop request will interrupt the playout and tear the PeerConnection down. It's very important to point out that no JSEP offer must be sent for replaying a recording: in this case, it will always be the plugin to generate a JSON offer (in response to a play request), which means you'll then have to provide a JSEP answer within the context of the following start request which will close the circle. Notice that play can be used to replay private recordings as well: a recording marked as private is simply not returned when retrieving the list of available recordings, but if the user is aware of a recording ID through other means, then that recording can be replayed as all other non-private recordings.

A play request has to be formatted as follows:

{
        "request" : "play",
        "id" : <unique numeric ID of the recording to replay>
}

This will result in a preparing status notification which will be attached to the JSEP offer originated by the plugin in order to match the media available in the recording:

{
        "recordplay" : "event",
        "result": {
                "status" : "preparing",
                "id" : <unique numeric ID of the recording>
        }
}

A start request, which as anticipated must be attached to the JSEP answer to the previous offer sent by the plugin, has to be formatted as follows:

{
        "request" : "start",
}

This will result in a playing status notification:

{
        "recordplay" : "event",
        "result": {
                "status" : "playing"
        }
}

Just as before, a stop request can interrupt the playout process at any time, and tear the associated PeerConnection down:

{
        "request" : "stop",
}

This will result in a stopped status:

{
        "recordplay" : "event",
        "result": {
                "status" : "stopped"
        }
}

If the plugin detects a loss of the associated PeerConnection, whether as a result of a stop request or because the connection was closed, a done result notification is triggered to inform the application the recording/playout session is over:

{
        "recordplay" : "event",
        "result": {
                "status" : "done",
                "id" : <unique numeric ID of the completed recording>,
                "is_private" : <whether the completed recording is private>
        }
}