WebRTC websocket implementation now uses generalized base version, removed unused code from MP4 output
This commit is contained in:
parent
b9819eb40f
commit
16e22eadbe
6 changed files with 19 additions and 255 deletions
File diff suppressed because one or more lines are too long
|
@ -273,7 +273,7 @@ p.prototype.build = function (MistVideo,callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ev.type in me.listeners) {
|
if (ev.type in me.listeners) {
|
||||||
return me.listeners[ev.type].call(me,ev);
|
return me.listeners[ev.type].call(me,("data" in ev)?ev.data:ev);
|
||||||
}
|
}
|
||||||
MistVideo.log("Unhandled WebRTC event "+ev.type+": "+JSON.stringify(ev));
|
MistVideo.log("Unhandled WebRTC event "+ev.type+": "+JSON.stringify(ev));
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -486,7 +486,7 @@ namespace Mist{
|
||||||
if (command.isMember("seek_time")){
|
if (command.isMember("seek_time")){
|
||||||
possiblyReselectTracks(command["seek_time"].asInt());
|
possiblyReselectTracks(command["seek_time"].asInt());
|
||||||
}else{
|
}else{
|
||||||
possiblyReselectTracks(currentTime());
|
if (parseData){possiblyReselectTracks(currentTime());}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,6 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fragSet{
|
|
||||||
uint64_t firstPart;
|
|
||||||
uint64_t lastPart;
|
|
||||||
uint64_t firstTime;
|
|
||||||
uint64_t lastTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OutMP4 : public HTTPOutput{
|
class OutMP4 : public HTTPOutput{
|
||||||
public:
|
public:
|
||||||
OutMP4(Socket::Connection &conn);
|
OutMP4(Socket::Connection &conn);
|
||||||
|
@ -143,9 +136,6 @@ namespace Mist{
|
||||||
int keysOnly;
|
int keysOnly;
|
||||||
uint64_t estimateFileSize() const;
|
uint64_t estimateFileSize() const;
|
||||||
|
|
||||||
// This is a dirty solution... but it prevents copying and copying and copying again
|
|
||||||
std::map<size_t, fragSet> currentPartSet;
|
|
||||||
|
|
||||||
std::string protectionHeader(size_t idx);
|
std::string protectionHeader(size_t idx);
|
||||||
Util::ResizeablePointer webBuf;
|
Util::ResizeablePointer webBuf;
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,10 +66,9 @@ namespace Mist{
|
||||||
vidTrack = INVALID_TRACK_ID;
|
vidTrack = INVALID_TRACK_ID;
|
||||||
prevVidTrack = INVALID_TRACK_ID;
|
prevVidTrack = INVALID_TRACK_ID;
|
||||||
audTrack = INVALID_TRACK_ID;
|
audTrack = INVALID_TRACK_ID;
|
||||||
stayLive = true;
|
|
||||||
target_rate = 0.0;
|
|
||||||
firstKey = true;
|
firstKey = true;
|
||||||
repeatInit = true;
|
repeatInit = true;
|
||||||
|
wsCmds = true;
|
||||||
|
|
||||||
lastTimeSync = 0;
|
lastTimeSync = 0;
|
||||||
maxSkipAhead = 0;
|
maxSkipAhead = 0;
|
||||||
|
@ -297,6 +296,12 @@ namespace Mist{
|
||||||
config->addOptionsFromCapabilities(capa);
|
config->addOptionsFromCapabilities(capa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OutWebRTC::onFail(const std::string &msg, bool critical){
|
||||||
|
if (!webSock){return HTTPOutput::onFail(msg, critical);}
|
||||||
|
sendSignalingError("error", msg);
|
||||||
|
Output::onFail(msg, critical);
|
||||||
|
}
|
||||||
|
|
||||||
void OutWebRTC::preWebsocketConnect(){
|
void OutWebRTC::preWebsocketConnect(){
|
||||||
HTTP::URL tmpUrl("http://" + H.GetHeader("Host"));
|
HTTP::URL tmpUrl("http://" + H.GetHeader("Host"));
|
||||||
externalAddr = tmpUrl.host;
|
externalAddr = tmpUrl.host;
|
||||||
|
@ -465,7 +470,6 @@ namespace Mist{
|
||||||
JSON::Value command = JSON::fromString(webSock->data, webSock->data.size());
|
JSON::Value command = JSON::fromString(webSock->data, webSock->data.size());
|
||||||
JSON::Value commandResult;
|
JSON::Value commandResult;
|
||||||
|
|
||||||
|
|
||||||
if(command.isMember("encrypt")){
|
if(command.isMember("encrypt")){
|
||||||
doDTLS = false;
|
doDTLS = false;
|
||||||
volkswagenMode = false;
|
volkswagenMode = false;
|
||||||
|
@ -570,164 +574,6 @@ namespace Mist{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<size_t> validTracks = M.getValidTracks();
|
|
||||||
if (command["type"] == "tracks"){
|
|
||||||
if (command.isMember("audio")){
|
|
||||||
if (!command["audio"].isNull()){
|
|
||||||
targetParams["audio"] = command["audio"].asString();
|
|
||||||
if (audTrack && command["audio"].asInt()){
|
|
||||||
uint64_t tId = command["audio"].asInt();
|
|
||||||
if (validTracks.count(tId) && M.getCodec(tId) != M.getCodec(audTrack)){
|
|
||||||
targetParams["audio"] = "none";
|
|
||||||
sendSignalingError("tracks", "Cannot select track because it is encoded as " +
|
|
||||||
M.getCodec(tId) + " but the already negotiated track is " +
|
|
||||||
M.getCodec(audTrack) + ". Please re-negotiate to play this track.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
targetParams.erase("audio");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (command.isMember("video")){
|
|
||||||
if (!command["video"].isNull()){
|
|
||||||
targetParams["video"] = command["video"].asString();
|
|
||||||
if (vidTrack && command["video"].asInt()){
|
|
||||||
uint64_t tId = command["video"].asInt();
|
|
||||||
if (validTracks.count(tId) && M.getCodec(tId) != M.getCodec(vidTrack)){
|
|
||||||
targetParams["video"] = "none";
|
|
||||||
sendSignalingError("tracks", "Cannot select track because it is encoded as " +
|
|
||||||
M.getCodec(tId) + " but the already negotiated track is " +
|
|
||||||
M.getCodec(vidTrack) + ". Please re-negotiate to play this track.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
targetParams.erase("video");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remember the previous video track, if any.
|
|
||||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
|
||||||
if (M.getType(it->first) == "video"){
|
|
||||||
prevVidTrack = it->first;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectDefaultTracks();
|
|
||||||
// Add the previous video track back, if we had one.
|
|
||||||
if (prevVidTrack != INVALID_TRACK_ID && !userSelect.count(prevVidTrack)){
|
|
||||||
uint64_t seekTarget = currentTime();
|
|
||||||
userSelect[prevVidTrack].reload(streamName, prevVidTrack);
|
|
||||||
seek(seekTarget);
|
|
||||||
}
|
|
||||||
onIdle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "seek"){
|
|
||||||
if (!command.isMember("seek_time")){
|
|
||||||
sendSignalingError("on_seek", "Received a seek request but no `seek_time` property.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint64_t seek_time = command["seek_time"].asInt();
|
|
||||||
if (!parseData){
|
|
||||||
parseData = true;
|
|
||||||
selectDefaultTracks();
|
|
||||||
}
|
|
||||||
stayLive = (target_rate == 0.0) && (endTime() < seek_time + 5000);
|
|
||||||
if (command["seek_time"].asStringRef() == "live"){stayLive = true;}
|
|
||||||
if (stayLive){seek_time = endTime();}
|
|
||||||
seek(seek_time, true);
|
|
||||||
JSON::Value commandResult;
|
|
||||||
commandResult["type"] = "on_seek";
|
|
||||||
commandResult["result"] = true;
|
|
||||||
if (M.getLive()){commandResult["live_point"] = stayLive;}
|
|
||||||
if (target_rate == 0.0){
|
|
||||||
commandResult["play_rate_curr"] = "auto";
|
|
||||||
}else{
|
|
||||||
commandResult["play_rate_curr"] = target_rate;
|
|
||||||
}
|
|
||||||
webSock->sendFrame(commandResult.toString());
|
|
||||||
onIdle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "set_speed"){
|
|
||||||
if (!command.isMember("play_rate")){
|
|
||||||
sendSignalingError("on_speed", "Received a playback speed setting request but no `play_rate` property.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
double set_rate = command["play_rate"].asDouble();
|
|
||||||
if (!parseData){
|
|
||||||
parseData = true;
|
|
||||||
selectDefaultTracks();
|
|
||||||
}
|
|
||||||
JSON::Value commandResult;
|
|
||||||
commandResult["type"] = "on_speed";
|
|
||||||
if (target_rate == 0.0){
|
|
||||||
commandResult["play_rate_prev"] = "auto";
|
|
||||||
}else{
|
|
||||||
commandResult["play_rate_prev"] = target_rate;
|
|
||||||
}
|
|
||||||
if (set_rate == 0.0){
|
|
||||||
commandResult["play_rate_curr"] = "auto";
|
|
||||||
}else{
|
|
||||||
commandResult["play_rate_curr"] = set_rate;
|
|
||||||
}
|
|
||||||
if (target_rate != set_rate){
|
|
||||||
target_rate = set_rate;
|
|
||||||
if (target_rate == 0.0){
|
|
||||||
realTime = 1000;//set playback speed to default
|
|
||||||
firstTime = Util::bootMS() - currentTime();
|
|
||||||
maxSkipAhead = 0;//enabled automatic rate control
|
|
||||||
}else{
|
|
||||||
stayLive = false;
|
|
||||||
//Set new realTime speed
|
|
||||||
realTime = 1000 / target_rate;
|
|
||||||
firstTime = Util::bootMS() - (currentTime() / target_rate);
|
|
||||||
maxSkipAhead = 1;//disable automatic rate control
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (M.getLive()){commandResult["live_point"] = stayLive;}
|
|
||||||
webSock->sendFrame(commandResult.toString());
|
|
||||||
onIdle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "pause"){
|
|
||||||
parseData = !parseData;
|
|
||||||
JSON::Value commandResult;
|
|
||||||
commandResult["type"] = "on_time";
|
|
||||||
commandResult["paused"] = !parseData;
|
|
||||||
commandResult["current"] = currentTime();
|
|
||||||
commandResult["begin"] = startTime();
|
|
||||||
commandResult["end"] = endTime();
|
|
||||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
|
||||||
commandResult["tracks"].append((uint64_t)it->first);
|
|
||||||
}
|
|
||||||
webSock->sendFrame(commandResult.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "hold") {
|
|
||||||
parseData = false;
|
|
||||||
JSON::Value commandResult;
|
|
||||||
commandResult["type"] = "on_time";
|
|
||||||
commandResult["paused"] = !parseData;
|
|
||||||
commandResult["current"] = currentTime();
|
|
||||||
commandResult["begin"] = startTime();
|
|
||||||
commandResult["end"] = endTime();
|
|
||||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
|
||||||
commandResult["tracks"].append((uint64_t)it->first);
|
|
||||||
}
|
|
||||||
webSock->sendFrame(commandResult.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "stop"){
|
|
||||||
INFO_MSG("Received stop() command.");
|
|
||||||
myConn.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command["type"] == "keyframe_interval"){
|
if (command["type"] == "keyframe_interval"){
|
||||||
if (!command.isMember("keyframe_interval_millis")){
|
if (!command.isMember("keyframe_interval_millis")){
|
||||||
sendSignalingError("on_keyframe_interval", "No keyframe_interval_millis attribute found.");
|
sendSignalingError("on_keyframe_interval", "No keyframe_interval_millis attribute found.");
|
||||||
|
@ -853,18 +699,8 @@ namespace Mist{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OutWebRTC::onIdle(){
|
void OutWebRTC::handleWebsocketIdle(){
|
||||||
if (parseData){
|
if (!parseData && isPushing()){
|
||||||
JSON::Value commandResult;
|
|
||||||
commandResult["type"] = "on_time";
|
|
||||||
commandResult["current"] = currentTime();
|
|
||||||
commandResult["begin"] = startTime();
|
|
||||||
commandResult["end"] = endTime();
|
|
||||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
|
||||||
commandResult["tracks"].append((uint64_t)it->first);
|
|
||||||
}
|
|
||||||
webSock->sendFrame(commandResult.toString());
|
|
||||||
}else if (isPushing()){
|
|
||||||
JSON::Value commandResult;
|
JSON::Value commandResult;
|
||||||
commandResult["type"] = "on_media_receive";
|
commandResult["type"] = "on_media_receive";
|
||||||
commandResult["millis"] = endTime();
|
commandResult["millis"] = endTime();
|
||||||
|
@ -876,7 +712,9 @@ namespace Mist{
|
||||||
commandResult["stats"]["jitter_ms"] = stats_jitter;
|
commandResult["stats"]["jitter_ms"] = stats_jitter;
|
||||||
commandResult["stats"]["loss_perc"] = stats_lossperc;
|
commandResult["stats"]["loss_perc"] = stats_lossperc;
|
||||||
webSock->sendFrame(commandResult.toString());
|
webSock->sendFrame(commandResult.toString());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
HTTPOutput::handleWebsocketIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OutWebRTC::onFinish(){
|
bool OutWebRTC::onFinish(){
|
||||||
|
@ -1677,18 +1515,12 @@ namespace Mist{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function was implemented (it's virtual) to handle
|
void OutWebRTC::sendNext(){
|
||||||
// pushing of media to the browser. This function blocks until
|
HTTPOutput::sendNext();
|
||||||
// the DTLS handshake has been finished. This prevents
|
|
||||||
// `sendNext()` from being called which is correct because we
|
|
||||||
// don't want to send packets when we can't protect them with
|
|
||||||
// DTLS.
|
|
||||||
void OutWebRTC::sendHeader(){
|
|
||||||
|
|
||||||
// first make sure that we complete the DTLS handshake.
|
// first make sure that we complete the DTLS handshake.
|
||||||
if(doDTLS){
|
if(doDTLS){
|
||||||
while (keepGoing() && !dtlsHandshake.hasKeyingMaterial()){
|
while (keepGoing() && !dtlsHandshake.hasKeyingMaterial()){
|
||||||
if (!handleWebRTCInputOutput()){udp.sendPaced(10000);}
|
if (!handleWebRTCInputOutput()){udp.sendPaced(10);}else{udp.sendPaced(0);}
|
||||||
if (lastRecv < Util::bootMS() - 10000){
|
if (lastRecv < Util::bootMS() - 10000){
|
||||||
WARN_MSG("Killing idle connection in handshake phase");
|
WARN_MSG("Killing idle connection in handshake phase");
|
||||||
onFail("idle connection in handshake phase", false);
|
onFail("idle connection in handshake phase", false);
|
||||||
|
@ -1696,10 +1528,6 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sentHeader = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OutWebRTC::sendNext(){
|
|
||||||
if (lastRecv < Util::bootMS() - 10000){
|
if (lastRecv < Util::bootMS() - 10000){
|
||||||
WARN_MSG("Killing idle connection");
|
WARN_MSG("Killing idle connection");
|
||||||
onFail("idle connection", false);
|
onFail("idle connection", false);
|
||||||
|
|
|
@ -1,57 +1,3 @@
|
||||||
/*
|
|
||||||
|
|
||||||
SOME NOTES ON MIST
|
|
||||||
|
|
||||||
- When a user wants to start pushing video into Mist we need to
|
|
||||||
check if the user is actually allowed to do this. When the user
|
|
||||||
is allowed to push we have to call the function `allowPush("")`.
|
|
||||||
|
|
||||||
SIGNALING
|
|
||||||
|
|
||||||
1. Client sends the offer:
|
|
||||||
|
|
||||||
{
|
|
||||||
type: "offer_sdp",
|
|
||||||
offer_sdp: <the-client-offer-sdp>,
|
|
||||||
}
|
|
||||||
|
|
||||||
Server responds with:
|
|
||||||
|
|
||||||
SUCCESS:
|
|
||||||
{
|
|
||||||
type: "on_answer_sdp",
|
|
||||||
result: true,
|
|
||||||
answer_sdp: <the-server-answer-sdp>,
|
|
||||||
}
|
|
||||||
|
|
||||||
ERROR:
|
|
||||||
{
|
|
||||||
type: "on_answer_sdp",
|
|
||||||
result: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
2. Client request new bitrate:
|
|
||||||
|
|
||||||
{
|
|
||||||
type: "video_bitrate"
|
|
||||||
video_bitrate: 600000
|
|
||||||
}
|
|
||||||
|
|
||||||
Server responds with:
|
|
||||||
|
|
||||||
SUCCESS:
|
|
||||||
{
|
|
||||||
type: "on_video_bitrate"
|
|
||||||
result: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ERROR:
|
|
||||||
{
|
|
||||||
type: "on_video_bitrate"
|
|
||||||
result: false
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "output.h"
|
#include "output.h"
|
||||||
|
@ -128,14 +74,14 @@ namespace Mist{
|
||||||
OutWebRTC(Socket::Connection &myConn);
|
OutWebRTC(Socket::Connection &myConn);
|
||||||
~OutWebRTC();
|
~OutWebRTC();
|
||||||
static void init(Util::Config *cfg);
|
static void init(Util::Config *cfg);
|
||||||
virtual void sendHeader();
|
|
||||||
virtual void sendNext();
|
virtual void sendNext();
|
||||||
virtual void onWebsocketFrame();
|
virtual void onWebsocketFrame();
|
||||||
virtual void respondHTTP(const HTTP::Parser & req, bool headersOnly);
|
virtual void respondHTTP(const HTTP::Parser & req, bool headersOnly);
|
||||||
virtual void preHTTP(){}
|
virtual void preHTTP(){}
|
||||||
virtual void preWebsocketConnect();
|
virtual void preWebsocketConnect();
|
||||||
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
|
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
|
||||||
void onIdle();
|
void handleWebsocketIdle();
|
||||||
|
virtual void onFail(const std::string &msg, bool critical = false);
|
||||||
bool onFinish();
|
bool onFinish();
|
||||||
bool doesWebsockets(){return true;}
|
bool doesWebsockets(){return true;}
|
||||||
void handleWebRTCInputOutputFromThread();
|
void handleWebRTCInputOutputFromThread();
|
||||||
|
|
Loading…
Add table
Reference in a new issue