WCS offers possibility to capture a media stream from an MP4 file located on the local disk of the server (Video on Demand, VOD). The received stream can be played, republished, managed just like any stream on the WCS server. First of all, this option is intended to play previously recorded broadcasts in a browsers or a mobile application on the client side.
Overview
To capture VOD from a file, specify a link to the vod file as a stream name when calling the session.createStream() function, as follows:
vod://sample.mp4
where sample.mp4 - is the name of the file that should be located in the /usr/local/FlashphonerWebCallServer/media/ directory.
If a file with such a name does not exist, the server returns the StreamStatusEvent FAILED message, where the "info" field has the reason: "File not found".
A stream created this way can be displayed to one user (personal VOD). If a full-featured online-broadcast is required, provide the link to a file as follows:
vod-live://sample.mp4
Multiple user can connect to such a stream simultaneously.
Supported formats and codecs
- Container: MP4
- Video: H.264
- Audio: AAC
Operation flowchart
- The browser connects to the server via Websocket and sends the publish command.
- The browser captures the microphone and the camera and sends the WebRTC stream as H.264 + AAC to the server, enabling recording with the parameter record: true.
- The WCS server records the stream to a file.
- The browser stops publishing.
- The second browser establishes a connection via Websocket, creates a stream, specifies the file name, and sends the play command.
- The second browser receives the WebRTC stream and plays this stream on the page.
Quick manual on testing
1. For the test we use the Player web application to play the file.
2. Upload the file to the /usr/local/FlashphonerWebCallServer/media/ directory.
3. Open the Player web application and enter the name of the file in the Stream field:
4. Click Start. The file starts playing:
5. Click Stop to stop the playback.
6. Delete the file from /usr/local/FlashphonerWebCallServer/media/
7. Click Start. You should see the FAILED status and the "File not found" message:
Call flow
Below is the call flow when using:
the Stream Recording example to publish the stream and record the file
the Player example to play the VOD stream
1. Establishing a connection to the server to publish and record the stream.
Flashphoner.createSession(); code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ ... });
2. Receiving from the server an event confirming successful connection.
ConnectionStatusEvent ESTABLISHED code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ setStatus(session.status()); //session connected, start playback publishStream(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ ... }).on(SESSION_STATUS.FAILED, function(){ ... });
3. Publishing the stream with recording enabled.
stream.publish(); code
session.createStream({ name: streamName, display: localVideo, record: true, receiveVideo: false, receiveAudio: false ... }).publish();
4. Receiving from the server an event confirming successful publishing of the stream.
StreamStatusEvent, status PUBLISHING code
session.createStream({ name: streamName, display: localVideo, record: true, receiveVideo: false, receiveAudio: false }).on(STREAM_STATUS.PUBLISHING, function(stream) { setStatus(stream.status()); onStarted(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { ... }).on(STREAM_STATUS.FAILED, function(stream) { ... }).publish();
5. Sending audio and video stream via WebRTC.
6. Stopping publishing the stream.
stream.stop(); code
function onStarted(stream) { $("#publishBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); }
7. Receiving from the server an event confirming unpublishing of the stream.
StreamStatusEvent, status UNPUBLISHED code
session.createStream({ name: streamName, display: localVideo, record: true, receiveVideo: false, receiveAudio: false }).on(STREAM_STATUS.PUBLISHING, function(stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { setStatus(stream.status()); showDownloadLink(stream.getRecordInfo()); onStopped(); }).on(STREAM_STATUS.FAILED, function(stream) { ... }).publish();
8. Establishing a connection to the server to play the stream.
Flashphoner.createSession(); code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ ... });
9. Receiving from the server an event confirming successful connection.
ConnectionStatusEvent ESTABLISHED code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ setStatus(session.status()); //session connected, start playback playStream(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ ... }).on(SESSION_STATUS.FAILED, function(){ ... });
10. Playing the stream.
stream.play(); code
if (Flashphoner.getMediaProviders()[0] === "MSE" && mseCutByIFrameOnly) { options.mediaConnectionConstraints = { cutByIFrameOnly: mseCutByIFrameOnly } } if (resolution_for_wsplayer) { options.playWidth = resolution_for_wsplayer.playWidth; options.playHeight = resolution_for_wsplayer.playHeight; } else if (resolution) { options.playWidth = resolution.split("x")[0]; options.playHeight = resolution.split("x")[1]; } stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }); stream.play();
11. Receiving from the server an event confirming successful playing of the stream.
StreamStatusEvent, status PLAYING code
stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }).on(STREAM_STATUS.PLAYING, function(stream) { $("#preloader").show(); setStatus(stream.status()); onStarted(stream); }).on(STREAM_STATUS.STOPPED, function() { ... }).on(STREAM_STATUS.FAILED, function(stream) { ... }).on(STREAM_STATUS.NOT_ENOUGH_BANDWIDTH, function(stream){ ... }); stream.play();
12. Receiving of the audio-video stream via Websocket and playing it via WebRTC
13. Stopping publishing the stream.
stream.stop(); code
function onStarted(stream) { $("#playBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); ... }
14. Receiving from the server an event confirming successful stopping of the playback of the stream.
StreamStatusEvent, status STOPPED code
stream = session.createStream(options).on(STREAM_STATUS.PENDING, function(stream) { ... }).on(STREAM_STATUS.PLAYING, function(stream) { ... }).on(STREAM_STATUS.STOPPED, function() { setStatus(STREAM_STATUS.STOPPED); onStopped(); }).on(STREAM_STATUS.FAILED, function(stream) { ... }).on(STREAM_STATUS.NOT_ENOUGH_BANDWIDTH, function(stream){ ... }); stream.play();
VOD loop
VOD live translation supports VOD loop: after end of file, capturing starts from file begin. This feature is enabled with the following parameter in flashphoner.properties file
vod_live_loop=true
VOD capturing from AWS S3
VOD stream can be captured from file placed to AWS S3 storage. Comparing with VOD capture from local disk, file from external storage is downloaded and captured sequentally.
To capture VOD from AWS S3 file, specify a link to the vod file as a stream name when calling the session.createStream() function, as follows:
vod://s3/bucket/sample.mp4
where
- bucket is S3 bucket name
- sample.mp4 is file name
Operation flowchart
1. Browser requests VOD capture from AWS file
2. WCS server sends request to AWS
3. File is downloaded to WCS server
4. WebRTC stream from file is sending to browser for playback
Set up
To download files from AWS S3 bucket, S3 credentials must be set in flashphoner.properties file
aws_s3_credentials=zone;login;hash
To capture stream from file while it is downloading, the following parameter should be set
vod_mp4_container_new=true
File format requirements
Header section (moov) should always be before data section (mdat). File structure should be like this:
Atom ftyp @ 0 of size: 32, ends @ 32 Atom moov @ 32 of size: 357961, ends @ 357993 ... Atom free @ 357993 of size: 8, ends @ 358001 Atom mdat @ 358001 of size: 212741950, ends @ 213099951
File structure can be checked with AtomicParsley utility
AtomicParsley file.mp4 -T 1
Wrong file structure can be fixed if necessary with ffmpeg without reencoding
ffmpeg -i bad.mp4 -acodec copy -vcodec copy -movflags +faststart good.mp4
VOD capture management with REST API
REST query should be HTTP/HTTPS POST request as:
- HTTP: http://test.flashphoner.com:8081/rest-api/vod/startup
- HTTPS: https://test.flashphoner.com:8444/rest-api/vod/startup
Where:
- test.flashphoner.com - WCS server address
- 8081 - standard REST / HTTP port
- 8444 - standard HTTPS port
- rest-api - mandatory part of URL
- /vod/startup - REST method used
REST queries and responses
REST query | REST query example | REST response example | Response states | Description |
---|---|---|---|---|
/vod/startup | { "uri":"vod-live://sample.mp4" "localStreamName": "test" } | 409 - Conflict 500 - Internal error | Capture VOD stream from file | |
/vod/find | { "localStreamName": "test" } | [ { "localMediaSessionId": "29ec3236-1093-42bb-88d6-d4ac37af3ac0", "localStreamName": "test", "uri": "vod-live://sample.mp4", "status": "PROCESSED_LOCAL", "hasAudio": true, "hasVideo": true, "record": false } ] | 200 – OK 404 – not found | Find VOD streams by criteria |
/vod/find_all | [ { "localMediaSessionId": "29ec3236-1093-42bb-88d6-d4ac37af3ac0", "localStreamName": "test", "uri": "vod-live://sample.mp4", "status": "PROCESSED_LOCAL", "hasAudio": true, "hasVideo": true, "record": false } ] | 200 – OK 404 – not found | Find all VOD streams | |
/vod/terminate | { "uri":"vod://sample.mp4" "localStreamName": "test" } | 200 - Stream is stopped 404 - Stream not found | Stop VOD stream |
Parameters
Name | Description | Example |
---|---|---|
uri | File name to capture | vod://sample.mp4 |
localStreamName | Stream name | test |
status | Stream status | PROCESSED_LOCAL |
localMediaSessionId | Mediasession Id | 29ec3236-1093-42bb-88d6-d4ac37af3ac0 |
hasAudio | Stream has audio | true |
hasVideo | Stream has video | true |
record | Stream is recording | false |
Known limits
/rest-api/vod/startup query can be used for VOD live translations creation only. However, find, find_all and terminate queries can be applied both to VOD and VOD live translations.
VOD stream publishing timeout after all subscribers gone off
By default, VOD stream stays published on server during 30 seconds after last subscriber gone off, if file duration exceeds this interval. This timeout can be changed with the following parameter
vod_stream_timeout=60000
In this case, VOD stream stays published during 60 seconds.
Known issues
1. AAC frames of type 0 are not supported by ffmpeg decoder and will be ignored while stream pulled playback
Symptoms: warnings in the client log:
10:13:06,815 WARN AAC - AudioProcessor-c6c22de8-a129-43b2-bf67-1f433a814ba9 Dropping AAC frame that starts with 0, 119056e500
Solution: switch to FDK AAC decoder
use_fdk_aac=true
2. Files with B-frames can be played unsmoothly, with artifacts and freezes
Symptoms: periodic freezes and artifacts while playing VOD file, warnongs in the client log
09:32:31,238 WARN 4BitstreamNormalizer - RTMP-pool-10-thread-5 It is B-frame!
Solution: reencode this file to exclude B-frames, for example
ffmpeg -i bad.mp4 -preset ultrafast -acodec copy -vcodec h264 -g 24 -bf 0 good.mp4