Skip to content

Injecting one stream into another

Overview

Since build 5.3.37 stream injection in WCS 5.3 branch is improved. A new REST API to inject a media file content or picture file into a stream published is added. Old API to inject streams is still available in WCS 5.2 branch

Supported codecs

Video:

  • H264

Audio:

  • AAC (source file audio)
  • Opus (target stream audio)
  • G711 (target stream audio)

Image

  • JPEG
  • PNG

Known limits and file requirements

  1. Injection cannot be applied to SIP call streams. Use the special audio and video injection technologies for SIP call streams.

  2. A sequental injection to the same stream is not supported. Previous injection must be terminated before a new injection.

  3. If audio codec in the file injected differs from stream audio codec, audio transcoding will be enabled on the server. That leads to a more CPU consumption.

  4. The file video track should be encoded without B-frames because they are not supported by browsers playing WebRTC.

  5. An encoder must be set up to place MP4 MOOV atom to the file header when preparing a file to download from HTTPS or S3 storage. Otherwise, file may be injected when it is fully downloaded only.

Injection REST API

REST query must be HTTP/HTTPS POST request as follows:

  • HTTP: http://test.flashphoner.com:8081/rest-api/stream/inject3/startup
  • HTTPS: https://test.flashphoner.com:8444/rest-api/stream/inject3/startup

Where:

  • test.flashphoner.com - WCS server address
  • 8081 - standard REST / HTTP port of  WCS server
  • 8444 - standard HTTPS port
  • rest-api - mandatory URL part
  • /stream/inject3/startup - REST method used

REST methods and responses

/stream/inject3/startup

Inject a media track or still image into a stream. Since build 5.3.44, you can also specify fps and gop for an image

Request example
POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/video.mp4",
            "media": "video"
        },
        {
            "uri": "file:///opt/media/audio.mp4",
            "media": "audio"
        }
    ],
    "audio": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "video": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "loop": true,
    "speed": 1,
    "duration": 20
}
Response example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Return codes
Code Reason
200 OK
400 Bad request
404 Not found
409 Conflict
500 Internal error

/stream/inject3/update

Since build 5.3.44 became possible to update the characteristics of the current injection

  • When updating duration, the duration is calculated relative to the time of the /stream/inject3/startup call
  • Parameters such as resources, audio, and video cannot be updated after injection into the stream
Request example
POST /rest-api/stream/inject3/update HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "fps": 1,
    "gop": 100,
    "speed": 0.5,
    "duration": 100,
    "loop": false
}
Response example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Return codes
Code Reason
200 OK
400 Bad request
404 Not found

/stream/inject3/find_all

Find all injections on the server. Answers will vary depending on the build

Request example
POST /rest-api/stream/inject3/find_all HTTP/1.1
Host: localhost:8081
Content-Type: application/json
Response example

Before build 5.3.44

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

[
    {
        "streamName": "test",
        "videoInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/video.mp4",
                "media": "video"
            },
            "startTime": 1749106490411
        },
        "audioInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/audio.mp4",
                "media": "audio"
            },
            "startTime": 1749106490410
        }
    }
]

Since build 5.3.44

  • The timer field is included in the response body only if duration was set via /stream/inject3/startup or /stream/inject3/update
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

[
    {
        "streamName": "test",
        "videoInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/img.png",
                "media": "image"
            },
            "startTime": 1749106490411,
            "fps": 1,
            "gop": 1
        },
        "audioInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/audio.mp4",
                "media": "audio"
            },
            "startTime": 1749106490410,
            "speed": 1.0
        },
        "loop": true,
        "timer": {
            "duration": 60,
            "left": 50
        }
    }
]
  • The fps and gop fields in the response body below are characteristics embedded in the mp4 file
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

[
    {
        "streamName": "test",
        "videoInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/audio_video.mp4",
                "media": "audio,video"
            },
            "startTime": 1753067834355,
            "speed": 1.0,
            "fps": 25,
            "gop": 75
        },
        "audioInjectorInfo": {
            "resource": {
                "uri": "file:///opt/media/audio_video.mp4",
                "media": "audio,video"
            },
            "startTime": 1753067834355,
            "speed": 1.0
        },
        "loop": true
    }
]
Return codes
Code Reason
200 OK
404 Not found

stream/inject3/terminate

Stop injection into stream. Requests will vary depending on the build

Request example

Before build 5.3.44 it is possible to stop inserting a separate track, and the original stream track will start playing

POST /rest-api/stream/inject3/terminate HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "video": true,
    "audio": true
}

Since build 5.3.44 the request completely aborts the insertion

POST /rest-api/stream/inject3/terminate HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test"
}
Response example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Return codes
Code Reason
200 OK
400 Bad request
404 Not found
500 Internal error

Parameters

Parameter Description Example
localStreamName Stream name to inject to test
resources Media resources list to inject [{"uri": "file:///opt/media/video.mp4", "media": "video"}]
-- uri Media resource URI file:///opt/media/video.mp4
-- media Media resource type to use video
audio Conditions to inject audio {"required": true, "wait": true, "stub": true}
video Conditions to inject video {"required": true, "wait": true, "stub": true}
-- required The source is required to start injection true
-- wait Should wait for track from another source (audio waits for video and vice versa) true
-- stub Should display black square or silence before source is ready true
loop Loop the injection false
duration Injection duration in seconds 20
speed Playback speed of the injected media [0.1, 4]
fps Frames per second (Applies only to image injection), available since build 5.3.44 [1, 100]
gop Number of frames in a group (Applies only to image injection), available since build 5.3.44 [1, 1000]
timer Returned in response to inject3/find_all request if duration was specified {"timer": {"duration":60, "left":54} }
-- duration Duration of injection 60
-- left Time remaining of injection since inject3/startup was called 54

Injection use cases

Video and audio injection

Video and audio tracks may be injected from the same source file

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/video.mp4",
            "media": "audio, video"
        }
    ],
    "audio": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "video": {
        "required": true,
        "wait": true,
        "stub": true
    }
}

or from two different sources

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/audio.mp4",
            "media": "audio"
        },
        {
            "uri": "file:///opt/media/video.mp4",
            "media": "video"
        }
    ],
    "audio": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "video": {
        "required": true,
        "wait": true,
        "stub": true
    }
}

If one of the sources is not ready yet (not fully downloaded for example) and wait parameter is set to true for another source, the injection will not start until the source becomes ready (fully downloaded and parsed).

If duration is set, the injection stops after the duration set. If not, the injection stops when one of the injected files ends. Anyway, the injection will stop if /stream/inject3/terminate is sent

POST /rest-api/stream/inject3/terminate HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test"
}

Video or audio only injection

Video or audio only may be injected, in this case the other track of the original stream will remain intouched.

In this case, only video will be replaced in the original stream

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/video.mp4",
            "media": "video"
        }
    ],
    "audio": {
        "required": false,
        "wait": false,
        "stub": false
    },
    "video": {
        "required": true,
        "wait": false,
        "stub": false
    }
}

And in this case only audio will be replaced

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/audio.mp4",
            "media": "audio"
        }
    ],
    "audio": {
        "required": true,
        "wait": false,
        "stub": false
    },
    "video": {
        "required": false,
        "wait": false,
        "stub": false
    }
}

Still image injection

A still image with audio may be injected

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/audio.mp4",
            "media": "audio"
        },
        {
            "uri": "file:///opt/media/image.png",
            "media": "image"
        }
    ],
    "audio": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "video": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "loop": true,
    "duration": 10
}

This is useful when viewers are waiting for the broadcast start, for example

Since build 5.3.44, you can also specify fps and gop for an image

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/audio.mp4",
            "media": "audio"
        },
        {
            "uri": "file:///opt/media/image.png",
            "media": "image"
        }
    ],
    "audio": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "video": {
        "required": true,
        "wait": true,
        "stub": true
    },
    "loop": true,
    "duration": 10,
    "fps": 1,
    "gop": 10
}

Looped injection

Upon reaching the end of a file, the resource is prepared for injection again based on the vod_mp4_container_new and cache configuration

When injecting audio and video from different files:

POST /rest-api/stream/inject3/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/short_audio.mp4",
            "media": "audio"
        },
        {
            "uri": "file:///opt/media/long_video.mp4",
            "media": "video"
        }
    ],
    "audio": {
        "required": true,
        "wait": false,
        "stub": false
    },
    "video": {
        "required": true,
        "wait": false,
        "stub": false
    },
    "loop": true
}

Before build 5.3.44, individual files were looped independently

Since build 5.3.44, looping is synchronized to the shortest file by duration. During resource preparation, a black screen and silence are injected into the original stream, and injection begins only after both resources are ready

File locations supported

The file to inject may be placed locally

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "file:///opt/media/video.mp4",
            "media": "audio, video"
        }
    ]
}

It also may be downloaded by HTTP/HTTPS from a third party CDN

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "https://cdn/downloads/video.mp4",
            "media": "audio, video"
        }
    ]
}

Or from an S3 compatible storage

{
    "localStreamName": "test",
    "resources": [
        {
            "uri": "s3/bucket/video.mp4",
            "media": "audio, video"
        }
    ]
}

File caching is supported for remote files (see cache configuration below)

WCS configuration

The new transcoding engine must be disabled before build 5.3.42

transcoding_gen3=false

It is recommended to enable periodic keyframe requests from WebRTC publishing browser for faster switching to the original stream when injection stops

periodic_fir_request=true

File reading on the fly while downloading should be enabled when using a remote files to prevent an excessive waiting

vod_mp4_container_new=true

S3 credentials configuration

When remote files are placed in S3 compatible storage, S3 access credentials should be set

AWS

To download files from AWS S3 bucket, S3 credentials must be set in flashphoner.properties file

aws_s3_credentials=zone;login;hash

Where

  • zone - AWS region where bucket is placed
  • login - Access Key ID
  • hash - Secret Access Key

S3 credentials setting example:

aws_s3_credentials=eu-central-1;AA22BB33CC44DE;DhlAkpZ4adclHhbLwhTNL4hvWTo80Njo

Digital Ocean Spaces

To download files from DO Spaces set the credentials as

aws_s3_credentials=ams3;access_key;secret

Where

  • ams3 - digitaloceanspaces.com subdomain
  • access_key - storage access key
  • secret - storage access secret code

Selectel

To download files from Selectel S3 set the credentials as

aws_s3_credentials=ru-1a;login;password

Where

  • ru-1a - storage region
  • login - user name
  • password - password

File cache configuration

Since build 5.3.38 a remote files may be cashed to reduce an incoming traffic and time to wait for downloading

http_client_cache_enabled=true
http_client_cache_path=.cache-http
http_client_max_cache_size=1g

In this case, the file cache will be placed to the folder /usr/local/FlashphonerWebCallServer/.cache-http. When all the files downloaded summary size exceeds 1 Gb, the oldest file or files will be deleted to free a disk space. The size is checked after a file download is finished, so at the moment the cache folder may occupy more dick space than the threshold set.

The cache folder should be writable by flashphoner user

drwxrwxr-x  2 flashphoner flashphoner 4096 Jun 19 00:28 .cache-http

The Cache-Control header may be set explicitly by the following parameter

http_client_cache_control=max-age=0

This may be useful to control a server behaviour while downloading a file.