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¶
-
Injection cannot be applied to SIP call streams. Use the special audio and video injection technologies for SIP call streams.
-
A sequental injection to the same stream is not supported. Previous injection must be terminated before a new injection.
-
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.
-
The file video track should be encoded without B-frames because they are not supported by browsers playing WebRTC.
-
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 address8081- standard REST / HTTP port of WCS server8444- standard HTTPS portrest-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¶
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/startupcall - Parameters such as
resources,audio, andvideocannot 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¶
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¶
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
timerfield is included in the response body only ifdurationwas set via/stream/inject3/startupor/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
fpsandgopfields in the response body below are characteristics embedded in themp4file
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¶
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
It is recommended to enable periodic keyframe requests from WebRTC publishing browser for faster switching to the original stream when injection stops
File reading on the fly while downloading should be enabled when using a remote files to prevent an excessive waiting
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
Where
zone- AWS region where bucket is placedlogin- Access Key IDhash- Secret Access Key
S3 credentials setting example:
Digital Ocean Spaces¶
To download files from DO Spaces set the credentials as
Where
ams3- digitaloceanspaces.com subdomainaccess_key- storage access keysecret- storage access secret code
Selectel¶
To download files from Selectel S3 set the credentials as
Where
ru-1a- storage regionlogin- user namepassword- 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
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
The Cache-Control header may be set explicitly by the following parameter
This may be useful to control a server behaviour while downloading a file.